shithub: candycrisis

Download patch

ref: 13aedf6a9faab2fe48c45278cd8280c76ec3816b
parent: 4ae0d05b2c6df1baee227d5b00e90e923c205119
author: CandyCrisis <>
date: Fri Nov 15 07:28:44 EST 2002

Candy Crisis 1.0 for Windows source code

diff: cannot open b/CandyCrisisResources//null: file does not exist: 'b/CandyCrisisResources//null' diff: cannot open b/SDL_main//null: file does not exist: 'b/SDL_main//null' diff: cannot open b/Source//null: file does not exist: 'b/Source//null'
--- /dev/null
+++ b/CandyCrisis.dev
@@ -1,0 +1,152 @@
+[Project]
+FileName=C:\Dev\CandyCrisis\CandyCrisis.dev
+Name=CandyCrisis
+Use_gpp=1
+UnitCount=59
+ResFiles=C:\Dev\CandyCrisis\Crisis.rc
+NoConsole=1
+IsDll=0
+Icon=C:\DevCpp\Icon\Mainicon.ico
+CompilerOptions=-lmingw32 -lSDL -lSDL_image -lfmod -funroll-loops
+IncludeDirs=C:\DevCpp\Include\SDL
+ObjFile=
+[Unit1]
+FileName=C:\Dev\CandyCrisis\Source\blitter.cpp
+[Unit2]
+FileName=C:\Dev\CandyCrisis\Source\blitter.h
+FileTime=762254186
+[Unit3]
+FileName=C:\Dev\CandyCrisis\Source\SDLU.h
+FileTime=762254268
+[Unit4]
+FileName=C:\Dev\CandyCrisis\Source\SDLU.cpp
+FileTime=760265985
+[Unit0]
+FileTime=761576470
+[Unit5]
+FileName=C:\Dev\CandyCrisis\Source\MTypes.cpp
+FileTime=761466198
+[Unit6]
+FileName=C:\Dev\CandyCrisis\Source\MTypes.h
+[Unit7]
+FileName=C:\Dev\CandyCrisis\Source\control.h
+[Unit9]
+FileName=C:\Dev\CandyCrisis\Source\gameticks.h
+[Unit10]
+FileName=C:\Dev\CandyCrisis\Source\graphics.h
+[Unit11]
+FileName=C:\Dev\CandyCrisis\Source\graymonitor.h
+[Unit12]
+FileName=C:\Dev\CandyCrisis\Source\grays.h
+[Unit13]
+FileName=C:\Dev\CandyCrisis\Source\gworld.h
+[Unit14]
+FileName=C:\Dev\CandyCrisis\Source\hiscore.h
+[Unit16]
+FileName=C:\Dev\CandyCrisis\Source\level.h
+FileTime=762254017
+[Unit17]
+FileName=C:\Dev\CandyCrisis\Source\main.h
+FileTime=760265652
+[Unit18]
+FileName=C:\Dev\CandyCrisis\Source\music.h
+[Unit19]
+FileName=C:\Dev\CandyCrisis\Source\moving.h
+[Unit20]
+FileName=C:\Dev\CandyCrisis\Source\next.h
+[Unit21]
+FileName=C:\Dev\CandyCrisis\Source\opponent.h
+[Unit22]
+FileName=C:\Dev\CandyCrisis\Source\pause.h
+[Unit23]
+FileName=C:\Dev\CandyCrisis\Source\players.h
+[Unit24]
+FileName=C:\Dev\CandyCrisis\Source\prefs.h
+[Unit25]
+FileName=C:\Dev\CandyCrisis\Source\random.h
+[Unit27]
+FileName=C:\Dev\CandyCrisis\Source\soundfx.h
+[Unit29]
+FileName=C:\Dev\CandyCrisis\Source\tweak.h
+[Unit30]
+FileName=C:\Dev\CandyCrisis\Source\victory.h
+[Unit31]
+FileName=C:\Dev\CandyCrisis\Source\zap.h
+FileTime=761597437
+[Unit33]
+FileName=C:\Dev\CandyCrisis\Source\font.cpp
+FileTime=760680850
+[Unit34]
+FileName=C:\Dev\CandyCrisis\Source\graphics.cpp
+FileTime=762252708
+[Unit35]
+FileName=C:\Dev\CandyCrisis\Source\gameticks.cpp
+[Unit8]
+FileName=C:\Dev\CandyCrisis\Source\font.h
+[Unit15]
+FileName=C:\Dev\CandyCrisis\Source\keyselect.h
+[Unit26]
+FileName=C:\Dev\CandyCrisis\Source\score.h
+FileTime=760284315
+[Unit28]
+FileName=C:\Dev\CandyCrisis\Source\tutorial.h
+[Unit36]
+FileName=C:\Dev\CandyCrisis\Source\graymonitor.cpp
+[Unit37]
+FileName=C:\Dev\CandyCrisis\Source\grays.cpp
+[Unit38]
+FileName=C:\Dev\CandyCrisis\Source\gworld.cpp
+[Unit39]
+FileName=C:\Dev\CandyCrisis\Source\hiscore.cpp
+FileTime=762254607
+[Unit40]
+FileName=C:\Dev\CandyCrisis\Source\main.cpp
+FileTime=761948219
+[Unit41]
+FileName=C:\Dev\CandyCrisis\Source\level.cpp
+FileTime=760810939
+[Unit42]
+FileName=C:\Dev\CandyCrisis\Source\keyselect.cpp
+FileTime=761332182
+[Unit32]
+FileName=C:\Dev\CandyCrisis\Source\control.cpp
+[Unit43]
+FileName=C:\Dev\CandyCrisis\Source\fmodmusic.cpp
+[Unit44]
+FileName=C:\Dev\CandyCrisis\Source\moving.cpp
+[Unit45]
+FileName=C:\Dev\CandyCrisis\Source\next.cpp
+[Unit46]
+FileName=C:\Dev\CandyCrisis\Source\opponent.cpp
+FileTime=762254036
+[Unit47]
+FileName=C:\Dev\CandyCrisis\Source\pause.cpp
+FileTime=761466338
+[Unit48]
+FileName=C:\Dev\CandyCrisis\Source\players.cpp
+[Unit49]
+FileName=C:\Dev\CandyCrisis\Source\prefs.cpp
+[Unit50]
+FileName=C:\Dev\CandyCrisis\Source\zap.cpp
+[Unit51]
+FileName=C:\Dev\CandyCrisis\Source\score.cpp
+[Unit52]
+FileName=C:\Dev\CandyCrisis\Source\tutorial.cpp
+[Unit53]
+FileName=C:\Dev\CandyCrisis\Source\tweak.cpp
+[Unit54]
+FileName=C:\Dev\CandyCrisis\Source\victory.cpp
+FileTime=761334713
+[Unit55]
+FileName=C:\Dev\CandyCrisis\Source\random.cpp
+FileTime=761531803
+[Unit56]
+FileName=C:\Dev\CandyCrisis\Source\fmodsoundfx.cpp
+FileTime=761335287
+[Unit57]
+FileName=C:\Dev\CandyCrisis\Source\RegAlgorithm.cpp
+[Unit58]
+FileName=C:\Dev\CandyCrisis\Source\RegAlgorithm.h
+FileTime=762254520
+[Unit59]
+FileName=C:\Dev\CandyCrisis\SDL_main\SDL_main.c
binary files /dev/null b/CandyCrisis.icns differ
binary files /dev/null b/CandyCrisisResources/PICT_1000.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_10000.png differ
binary files /dev/null b/CandyCrisisResources/PICT_10001.png differ
binary files /dev/null b/CandyCrisisResources/PICT_1001.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1002.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1004.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1100.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1101.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1102.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1104.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1200.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1201.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1202.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1204.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1300.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1301.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1302.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1304.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1400.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1401.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1402.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1404.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1500.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1501.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1502.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1504.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1505.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1600.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1601.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1602.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1604.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1700.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1701.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1702.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1704.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1800.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1801.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1802.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1804.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1805.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1900.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1901.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1902.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_1904.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_200.png differ
binary files /dev/null b/CandyCrisisResources/PICT_2000.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_2001.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_2002.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_2004.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_201.png differ
binary files /dev/null b/CandyCrisisResources/PICT_202.png differ
binary files /dev/null b/CandyCrisisResources/PICT_203.png differ
binary files /dev/null b/CandyCrisisResources/PICT_204.png differ
binary files /dev/null b/CandyCrisisResources/PICT_205.png differ
binary files /dev/null b/CandyCrisisResources/PICT_206.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_2100.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_2101.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_2102.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_2104.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_2105.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_2300.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_2301.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_2302.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_250.png differ
binary files /dev/null b/CandyCrisisResources/PICT_251.png differ
binary files /dev/null b/CandyCrisisResources/PICT_252.png differ
binary files /dev/null b/CandyCrisisResources/PICT_253.png differ
binary files /dev/null b/CandyCrisisResources/PICT_254.png differ
binary files /dev/null b/CandyCrisisResources/PICT_255.png differ
binary files /dev/null b/CandyCrisisResources/PICT_256.png differ
binary files /dev/null b/CandyCrisisResources/PICT_257.png differ
binary files /dev/null b/CandyCrisisResources/PICT_258.png differ
binary files /dev/null b/CandyCrisisResources/PICT_259.png differ
binary files /dev/null b/CandyCrisisResources/PICT_260.png differ
binary files /dev/null b/CandyCrisisResources/PICT_300.png differ
binary files /dev/null b/CandyCrisisResources/PICT_301.png differ
binary files /dev/null b/CandyCrisisResources/PICT_302.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_303.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_304.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_305.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_306.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_307.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_308.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_309.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_310.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_500.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_5000.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5001.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5002.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5003.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5004.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5005.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5006.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5007.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5008.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5009.png differ
binary files /dev/null b/CandyCrisisResources/PICT_501.jpg differ
binary files /dev/null b/CandyCrisisResources/PICT_5010.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5011.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5013.png differ
binary files /dev/null b/CandyCrisisResources/PICT_502.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5100.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5101.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5102.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5103.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5104.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5105.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5106.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5107.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5108.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5109.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5110.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5111.png differ
binary files /dev/null b/CandyCrisisResources/PICT_5113.png differ
--- /dev/null
+++ b/CandyCrisisResources/Preferences.txt
@@ -1,0 +1,7 @@
+mod : 01
+sfx : 01
+size: 000000000000000000000000000000000000
+keys: 6100000064000000780000007300000014010000130100001201000011010000
+high: 4C657669617468616E00000000000000000000000000000000000000000000000000000000000000409C000044722E20437269736973000000000000000000000000000000000000000000000000000000000000A08C0000416E67656C0000000000000000000000000000000000000000000000000000000000000000000000007D00005370696B650000000000000000000000000000000000000000000000000000000000000000000000606D0000466F7800000000000000000000000000000000000000000000000000000000000000000000000000C05D000052616775656C00000000000000000000000000000000000000000000000000000000000000000000204E00004B756D6F000000000000000000000000000000000000000000000000000000000000000000000000803E000050617474790000000000000000000000000000000000000000000000000000000000000000000000E02E000059757572656900000000000000000000000000000000000000000000000000000000000000000000401F0000476C7572700000000000000000000000000000000000000000000000000000000000000000000000A00F0000
+cmbx: 000000000000000000000000000000000000000000000000000000000000000000000101010202000000000000000000000001010000000000000000000000000000000000000000000000000000020200000E0103000000EA0100005475746F7269616C0000000000000000000000000000000000000000000000000000000000000000
+user: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
binary files /dev/null b/CandyCrisisResources/mod_128 differ
binary files /dev/null b/CandyCrisisResources/mod_129 differ
binary files /dev/null b/CandyCrisisResources/mod_130 differ
binary files /dev/null b/CandyCrisisResources/mod_131 differ
binary files /dev/null b/CandyCrisisResources/mod_132 differ
binary files /dev/null b/CandyCrisisResources/mod_133 differ
binary files /dev/null b/CandyCrisisResources/mod_134 differ
binary files /dev/null b/CandyCrisisResources/mod_135 differ
binary files /dev/null b/CandyCrisisResources/mod_136 differ
binary files /dev/null b/CandyCrisisResources/mod_137 differ
binary files /dev/null b/CandyCrisisResources/mod_138 differ
binary files /dev/null b/CandyCrisisResources/mod_139 differ
binary files /dev/null b/CandyCrisisResources/mod_140 differ
binary files /dev/null b/CandyCrisisResources/mod_141 differ
binary files /dev/null b/CandyCrisisResources/mod_142 differ
binary files /dev/null b/CandyCrisisResources/snd_128.wav differ
binary files /dev/null b/CandyCrisisResources/snd_129.wav differ
binary files /dev/null b/CandyCrisisResources/snd_130.wav differ
binary files /dev/null b/CandyCrisisResources/snd_131.wav differ
binary files /dev/null b/CandyCrisisResources/snd_132.wav differ
binary files /dev/null b/CandyCrisisResources/snd_133.wav differ
binary files /dev/null b/CandyCrisisResources/snd_134.wav differ
binary files /dev/null b/CandyCrisisResources/snd_135.wav differ
binary files /dev/null b/CandyCrisisResources/snd_136.wav differ
binary files /dev/null b/CandyCrisisResources/snd_137.wav differ
binary files /dev/null b/CandyCrisisResources/snd_138.wav differ
binary files /dev/null b/CandyCrisisResources/snd_139.wav differ
binary files /dev/null b/CandyCrisisResources/snd_140.wav differ
binary files /dev/null b/CandyCrisisResources/snd_141.wav differ
binary files /dev/null b/CandyCrisisResources/snd_142.wav differ
binary files /dev/null b/CandyCrisisResources/snd_143.wav differ
binary files /dev/null b/CandyCrisisResources/snd_144.wav differ
binary files /dev/null b/Crisis.ico differ
--- /dev/null
+++ b/Crisis.rc
@@ -1,0 +1,1 @@
+Crisis ICON "C:/Dev/CandyCrisis/Crisis.ico"
--- /dev/null
+++ b/CrisisGen.dev
@@ -1,0 +1,22 @@
+[Project]
+FileName=C:\Dev\CandyCrisis\CrisisGen.dev
+Name=CrisisGen
+Use_gpp=1
+UnitCount=3
+ResFiles=
+NoConsole=0
+IsDll=0
+Icon=C:\DevCpp\Bin\Mainicon.ico
+CompilerOptions=
+IncludeDirs=
+ObjFile=
+[Unit1]
+FileName=C:\Dev\CandyCrisis\Source\RegAlgorithm.h
+FileTime=761464410
+[Unit2]
+FileName=C:\Dev\CandyCrisis\Source\RegAlgorithm.cpp
+FileTime=761464174
+[Unit3]
+FileName=C:\Dev\CandyCrisis\Source\RegGenerator.cpp
+[Unit0]
+FileTime=761335618
binary files /dev/null b/SDL.dll differ
binary files /dev/null b/SDL_image.dll differ
--- /dev/null
+++ b/SDL_main/SDL_main.c
@@ -1,0 +1,337 @@
+/*
+    SDL_main.c, placed in the public domain by Sam Lantinga  4/13/98
+
+    The WinMain function -- calls your program's main() function 
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include <windows.h>
+#include <malloc.h>		/* For _alloca() */
+#include <io.h>         /* For _getcwd() */
+
+/* Include the SDL main definition header */
+#include "SDL.h"
+#include "SDL_main.h"
+#ifdef main
+#ifndef _WIN32_WCE_EMULATION
+#undef main
+#endif
+#endif
+
+/* Do we really not want stdio redirection with Windows CE? */
+#if 1 || defined(_WIN32_WCE)
+#define NO_STDIO_REDIRECT
+#endif
+
+/* The standard output files */
+#define STDOUT_FILE	TEXT("stdout.txt")
+#define STDERR_FILE	TEXT("stderr.txt")
+
+#ifndef NO_STDIO_REDIRECT
+static char stdoutPath[MAX_PATH];
+static char stderrPath[MAX_PATH];
+#endif
+
+#if defined(_WIN32_WCE) && _WIN32_WCE < 300
+/* seems to be undefined in Win CE although in online help */
+#define isspace(a) (((CHAR)a == ' ') || ((CHAR)a == '\t'))
+
+/* seems to be undefined in Win CE although in online help */
+char *strrchr(char *str, int c)
+{
+	char *p;
+
+	/* Skip to the end of the string */
+	p=str;
+	while (*p)
+		p++;
+
+	/* Look for the given character */
+	while ( (p >= str) && (*p != (CHAR)c) )
+		p--;
+
+	/* Return NULL if character not found */
+	if ( p < str ) {
+		p = NULL;
+	}
+	return p;
+}
+#endif /* _WIN32_WCE < 300 */
+
+/* Parse a command line buffer into arguments */
+static int ParseCommandLine(char *cmdline, char **argv)
+{
+	char *bufp;
+	int argc;
+
+	argc = 0;
+	for ( bufp = cmdline; *bufp; ) {
+		/* Skip leading whitespace */
+		while ( isspace(*bufp) ) {
+			++bufp;
+		}
+		/* Skip over argument */
+		if ( *bufp == '"' ) {
+			++bufp;
+			if ( *bufp ) {
+				if ( argv ) {
+					argv[argc] = bufp;
+				}
+				++argc;
+			}
+			/* Skip over word */
+			while ( *bufp && (*bufp != '"') ) {
+				++bufp;
+			}
+		} else {
+			if ( *bufp ) {
+				if ( argv ) {
+					argv[argc] = bufp;
+				}
+				++argc;
+			}
+			/* Skip over word */
+			while ( *bufp && ! isspace(*bufp) ) {
+				++bufp;
+			}
+		}
+		if ( *bufp ) {
+			if ( argv ) {
+				*bufp = '\0';
+			}
+			++bufp;
+		}
+	}
+	if ( argv ) {
+		argv[argc] = NULL;
+	}
+	return(argc);
+}
+
+/* Show an error message */
+static void ShowError(const char *title, const char *message)
+{
+/* If USE_MESSAGEBOX is defined, you need to link with user32.lib */
+#ifdef USE_MESSAGEBOX
+	MessageBox(NULL, message, title, MB_ICONEXCLAMATION|MB_OK);
+#else
+	fprintf(stderr, "%s: %s\n", title, message);
+#endif
+}
+
+/* Pop up an out of memory message, returns to Windows */
+static BOOL OutOfMemory(void)
+{
+	ShowError("Fatal Error", "Out of memory - aborting");
+	return FALSE;
+}
+
+/* Remove the output files if there was no output written */
+static void __cdecl cleanup_output(void)
+{
+#ifndef NO_STDIO_REDIRECT
+	FILE *file;
+	int empty;
+#endif
+
+	/* Flush the output in case anything is queued */
+	fclose(stdout);
+	fclose(stderr);
+
+#ifndef NO_STDIO_REDIRECT
+	/* See if the files have any output in them */
+	file = fopen(stdoutPath, "rb");
+	if ( file ) {
+		empty = (fgetc(file) == EOF) ? 1 : 0;
+		fclose(file);
+		if ( empty ) {
+			remove(stdoutPath);
+		}
+	}
+	file = fopen(stderrPath, "rb");
+	if ( file ) {
+		empty = (fgetc(file) == EOF) ? 1 : 0;
+		fclose(file);
+		if ( empty ) {
+			remove(stderrPath);
+		}
+	}
+#endif
+}
+
+#if defined(_MSC_VER) && !defined(_WIN32_WCE)
+/* The VC++ compiler needs main defined */
+#define console_main main
+#endif
+
+/* This is where execution begins [console apps] */
+int console_main(int argc, char *argv[])
+{
+	int n;
+	char *bufp, *appname;
+
+	/* Get the class name from argv[0] */
+	appname = argv[0];
+	if ( (bufp=strrchr(argv[0], '\\')) != NULL ) {
+		appname = bufp+1;
+	} else
+	if ( (bufp=strrchr(argv[0], '/')) != NULL ) {
+		appname = bufp+1;
+	}
+
+	if ( (bufp=strrchr(appname, '.')) == NULL )
+		n = strlen(appname);
+	else
+		n = (bufp-appname);
+
+	bufp = (char *)alloca(n+1);
+	if ( bufp == NULL ) {
+		return OutOfMemory();
+	}
+	strncpy(bufp, appname, n);
+	bufp[n] = '\0';
+	appname = bufp;
+
+	/* Load SDL dynamic link library */
+	if ( SDL_Init(SDL_INIT_NOPARACHUTE) < 0 ) {
+		ShowError("WinMain() error", SDL_GetError());
+		return(FALSE);
+	}
+	atexit(cleanup_output);
+	atexit(SDL_Quit);
+
+#ifndef DISABLE_VIDEO
+#if 0
+	/* Create and register our class *
+	   DJM: If we do this here, the user nevers gets a chance to
+	   putenv(SDL_WINDOWID).  This is already called later by
+	   the (DIB|DX5)_CreateWindow function, so it should be
+	   safe to comment it out here.
+	if ( SDL_RegisterApp(appname, CS_BYTEALIGNCLIENT, 
+	                     GetModuleHandle(NULL)) < 0 ) {
+		ShowError("WinMain() error", SDL_GetError());
+		exit(1);
+	}*/
+#else
+	/* Sam:
+	   We still need to pass in the application handle so that
+	   DirectInput will initialize properly when SDL_RegisterApp()
+	   is called later in the video initialization.
+	 */
+	SDL_SetModuleHandle(GetModuleHandle(NULL));
+#endif /* 0 */
+#endif /* !DISABLE_VIDEO */
+
+	/* Run the application main() code */
+	SDL_main(argc, argv);
+
+	/* Exit cleanly, calling atexit() functions */
+	exit(0);
+
+	/* Hush little compiler, don't you cry... */
+	return(0);
+}
+
+/* This is where execution begins [windowed apps] */
+#ifdef _WIN32_WCE
+int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPWSTR szCmdLine, int sw)
+#else
+int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
+#endif
+{
+	HINSTANCE handle;
+	char **argv;
+	int argc;
+	char *cmdline;
+#ifdef _WIN32_WCE
+	wchar_t *bufp;
+	int nLen;
+#else
+	char *bufp;
+#endif
+#ifndef NO_STDIO_REDIRECT
+	FILE *newfp;
+#endif
+
+	/* Start up DDHELP.EXE before opening any files, so DDHELP doesn't
+	   keep them open.  This is a hack.. hopefully it will be fixed 
+	   someday.  DDHELP.EXE starts up the first time DDRAW.DLL is loaded.
+	 */
+	handle = LoadLibrary(TEXT("DDRAW.DLL"));
+	if ( handle != NULL ) {
+		FreeLibrary(handle);
+	}
+
+#ifndef NO_STDIO_REDIRECT
+    _getcwd( stdoutPath, sizeof( stdoutPath ) );
+    strcat( stdoutPath, "\\" STDOUT_FILE );
+    
+	/* Redirect standard input and standard output */
+	newfp = freopen(stdoutPath, "w", stdout);
+	if ( newfp == NULL ) {	/* This happens on NT */
+#if !defined(stdout)
+		stdout = fopen(stdoutPath, "w");
+#else
+		newfp = fopen(stdoutPath, "w");
+		if ( newfp ) {
+			*stdout = *newfp;
+		}
+#endif
+	}
+
+    _getcwd( stderrPath, sizeof( stderrPath ) );
+    strcat( stderrPath, "\\" STDERR_FILE );
+
+	newfp = freopen(stderrPath, "w", stderr);
+	if ( newfp == NULL ) {	/* This happens on NT */
+#if !defined(stderr)
+		stderr = fopen(stderrPath, "w");
+#else
+		newfp = fopen(stderrPath, "w");
+		if ( newfp ) {
+			*stderr = *newfp;
+		}
+#endif
+	}
+	setvbuf(stdout, NULL, _IOLBF, BUFSIZ);	/* Line buffered */
+	setbuf(stderr, NULL);			/* No buffering */
+#endif /* !NO_STDIO_REDIRECT */
+
+#ifdef _WIN32_WCE
+	nLen = wcslen(szCmdLine)+128+1;
+	bufp = (wchar_t *)alloca(nLen*2);
+	wcscpy (bufp, TEXT("\""));
+	GetModuleFileName(NULL, bufp+1, 128-3);
+	wcscpy (bufp+wcslen(bufp), TEXT("\" "));
+	wcsncpy(bufp+wcslen(bufp), szCmdLine,nLen-wcslen(bufp));
+	nLen = wcslen(bufp)+1;
+	cmdline = (char *)alloca(nLen);
+	if ( cmdline == NULL ) {
+		return OutOfMemory();
+	}
+	WideCharToMultiByte(CP_ACP, 0, bufp, -1, cmdline, nLen, NULL, NULL);
+#else
+	/* Grab the command line (use alloca() on Windows) */
+	bufp = GetCommandLine();
+	cmdline = (char *)alloca(strlen(bufp)+1);
+	if ( cmdline == NULL ) {
+		return OutOfMemory();
+	}
+	strcpy(cmdline, bufp);
+#endif
+
+	/* Parse it into argv and argc */
+	argc = ParseCommandLine(cmdline, NULL);
+	argv = (char **)alloca((argc+1)*(sizeof *argv));
+	if ( argv == NULL ) {
+		return OutOfMemory();
+	}
+	ParseCommandLine(cmdline, argv);
+
+	/* Run the main program (after a little SDL initialization) */
+	return(console_main(argc, argv));
+}
--- /dev/null
+++ b/Source/MTypes.cpp
@@ -1,0 +1,49 @@
+///
+///  MTypes.c
+///
+///  Generic replacements for very basic Mac types.
+///
+///  John Stiles, 2002/10/14
+///
+
+
+#include "MTypes.h"
+
+
+static inline short min( short a, short b )
+{	
+	return (a < b)? a: b;
+}
+
+
+static inline short max( short a, short b )
+{	
+	return (a > b)? a: b;
+}
+
+
+void UnionMRect( const MRect* a, const MRect* b, MRect* u )
+{
+	u->top    = min( a->top, b->top );
+	u->left   = min( a->left, b->left );
+	u->bottom = max( a->bottom, b->bottom );
+	u->right  = max( a->right, b->right );
+}
+
+
+void OffsetMRect( MRect* r, int x, int y )
+{
+	r->top += y;
+	r->left += x;
+	r->bottom += y;
+	r->right += x;
+}
+
+
+unsigned char MPointInMRect( MPoint p, MRect* r )
+{
+	return (p.h >= r->left) &&
+	       (p.h <  r->right) &&
+	       (p.v >= r->top) &&
+	       (p.v <  r->bottom);
+}
--- /dev/null
+++ b/Source/MTypes.h
@@ -1,0 +1,46 @@
+/////
+///  MTypes.h
+///
+///  Generic replacements for very basic Mac types.
+///
+///  John Stiles, 2002/10/14
+///
+
+
+#ifndef __MTYPES__
+#define __MTYPES__
+
+
+typedef signed char MBoolean;
+
+
+typedef struct MRGBColor
+{
+	unsigned short red;
+	unsigned short green;
+	unsigned short blue;
+} MRGBColor;
+
+
+typedef struct MRect
+{
+	short top;
+	short left;
+	short bottom;
+	short right;	
+} MRect;
+
+
+typedef struct MPoint
+{
+	short v;
+	short h;
+} MPoint;
+
+
+void UnionMRect( const MRect* a, const MRect* b, MRect* u );
+void OffsetMRect( MRect* r, int x, int y );
+unsigned char MPointInMRect( MPoint p, MRect* r );
+
+
+#endif
--- /dev/null
+++ b/Source/Makefile
@@ -1,0 +1,45 @@
+
+OPTIMIZE := -O3
+CXXFLAGS := $(shell sdl-config --cflags) -DHAVE_MIKMOD $(OPTIMIZE)
+LIBS   := $(shell sdl-config --libs) -lSDL_image -lmikmod
+
+TARGET := CandyCrisis
+
+OBJECTS :=	\
+	blitter.o \
+	control.o \
+	font.o \
+	gameticks.o \
+	graphics.o \
+	graymonitor.o \
+	grays.o \
+	gworld.o \
+	hiscore.o \
+	keyselect.o \
+	level.o \
+	main.o \
+	midi.o \
+	moving.o \
+	MTypes.o \
+	next.o \
+	opponent.o \
+	pause.o \
+	players.o \
+	prefs.o \
+	random.o \
+	score.o \
+	SDLU.o \
+	soundfx.o \
+	tutorial.o \
+	tweak.o \
+	victory.o \
+	zap.o
+
+$(TARGET): $(OBJECTS)
+	$(CXX) -o $@ $^ $(LIBS)
+
+clean:
+	$(RM) $(OBJECTS)
+
+distclean: clean
+	$(RM) $(TARGET)
--- /dev/null
+++ b/Source/RegAlgorithm.cpp
@@ -1,0 +1,268 @@
+///
+///  RegAlgorithm.cpp
+///
+///  Registration code algorithm based on hashing functions at
+///  http://burtleburtle.net/bob/hash/#lookup
+///
+///  John Stiles, 2002/10/31
+///
+
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "MTypes.h"
+#include "RegAlgorithm.h"
+
+
+// These bits are completely random, generated from atmospheric noise.
+// (http://www.random.org/) They have no significance other than being 
+// seeds to the hash function below. 
+const unsigned int keylist[13] = {  0x40691e27,
+									0xe451380b,
+									0x603bb484,
+									0x93e4865e,
+									0x964c9a5d,
+									0xc19591e2,
+									0xff90b43a,
+									0x0b34b5c3,
+									0x5ed19fb7,
+									0xdcb4dcaf,
+									0x6700fcfa,
+									0x7c7ee303,
+									0xe2016ffc  }; 
+
+
+// The letters that the end user will receive in their code. They correspond
+// to hex digits [0-F] consecutively. Notice that the letters contain no vowels
+// (to avoid making comprehensible words), and no overtly ambiguous characters 
+// (O's, I's, L's, zeros, ones, etc).
+const char* scrambleKey = "PWCDRSHBNTVFGJMZ";
+
+/*
+--------------------------------------------------------------------
+mix -- mix 3 32-bit values reversibly.
+For every delta with one or two bit set, and the deltas of all three
+  high bits or all three low bits, whether the original value of a,b,c
+  is almost all zero or is uniformly distributed,
+* If mix() is run forward or backward, at least 32 bits in a,b,c
+  have at least 1/4 probability of changing.
+* If mix() is run forward, every bit of c will change between 1/3 and
+  2/3 of the time.  (Well, 22/100 and 78/100 for some 2-bit deltas.)
+mix() was built out of 36 single-cycle latency instructions in a 
+  structure that could supported 2x parallelism, like so:
+      a -= b; 
+      a -= c; x = (c>>13);
+      b -= c; a ^= x;
+      b -= a; x = (a<<8);
+      c -= a; b ^= x;
+      c -= b; x = (b>>13);
+      ...
+  Unfortunately, superscalar Pentiums and Sparcs can't take advantage 
+  of that parallelism.  They've also turned some of those single-cycle
+  latency instructions into multi-cycle latency instructions.  Still,
+  this is the fastest good hash I could find.  There were about 2^^68
+  to choose from.  I only looked at a billion or so.
+--------------------------------------------------------------------
+*/
+
+#define mix(a,b,c) \
+{ \
+  a -= b; a -= c; a ^= (c>>13); \
+  b -= c; b -= a; b ^= (a<<8); \
+  c -= a; c -= b; c ^= (b>>13); \
+  a -= b; a -= c; a ^= (c>>12);  \
+  b -= c; b -= a; b ^= (a<<16); \
+  c -= a; c -= b; c ^= (b>>5); \
+  a -= b; a -= c; a ^= (c>>3);  \
+  b -= c; b -= a; b ^= (a<<10); \
+  c -= a; c -= b; c ^= (b>>15); \
+}
+
+
+/*
+--------------------------------------------------------------------
+hash() -- hash a variable-length key into a 32-bit value
+  k     : the key (the unaligned variable-length array of bytes)
+  len   : the length of the key, counting by bytes
+  level : can be any 4-byte value
+Returns a 32-bit value.  Every bit of the key affects every bit of
+the return value.  Every 1-bit and 2-bit delta achieves avalanche.
+About 36+6len instructions.
+
+The best hash table sizes are powers of 2.  There is no need to do
+mod a prime (mod is sooo slow!).  If you need less than 32 bits,
+use a bitmask.  For example, if you need only 10 bits, do
+  h = (h & hashmask(10));
+In which case, the hash table should have hashsize(10) elements.
+
+If you are hashing n strings (ub1 **)k, do it like this:
+  for (i=0, h=0; i<n; ++i) h = hash( k[i], len[i], h);
+
+By Bob Jenkins, 1996.  [email protected].  You may use this
+code any way you wish, private, educational, or commercial.  It's free.
+
+See http://burlteburtle.net/bob/hash/evahash.html
+Use for hash table lookup, or anything where one collision in 2^32 is
+acceptable.  Do NOT use for cryptographic purposes.
+--------------------------------------------------------------------
+*/
+
+unsigned int hash( unsigned char* k, unsigned int length, unsigned int initval )
+{
+   unsigned int a, b, c, len;
+
+   /* Set up the internal state */
+   len = length;
+   a = b = 0x9e3779b9;  /* the golden ratio; an arbitrary value */
+   c = initval;           /* the previous hash value */
+
+   /*---------------------------------------- handle most of the key */
+   while (len >= 12)
+   {
+      a += (k[0] + ((unsigned int)k[1]<<8) + ((unsigned int)k[2]<<16)  + ((unsigned int)k[3]<<24));
+      b += (k[4] + ((unsigned int)k[5]<<8) + ((unsigned int)k[6]<<16)  + ((unsigned int)k[7]<<24));
+      c += (k[8] + ((unsigned int)k[9]<<8) + ((unsigned int)k[10]<<16) + ((unsigned int)k[11]<<24));
+      mix(a,b,c);
+      k += 12; len -= 12;
+   }
+
+   /*------------------------------------- handle the last 11 bytes */
+   c += length;
+   switch(len)              /* all the case statements fall through */
+   {
+	   case 11: c+=((unsigned int)k[10]<<24);
+	   case 10: c+=((unsigned int)k[9]<<16);
+	   case 9 : c+=((unsigned int)k[8]<<8);
+	      /* the first byte of c is reserved for the length */
+	   case 8 : b+=((unsigned int)k[7]<<24);
+	   case 7 : b+=((unsigned int)k[6]<<16);
+	   case 6 : b+=((unsigned int)k[5]<<8);
+	   case 5 : b+=k[4];
+	   case 4 : a+=((unsigned int)k[3]<<24);
+	   case 3 : a+=((unsigned int)k[2]<<16);
+	   case 2 : a+=((unsigned int)k[1]<<8);
+	   case 1 : a+=k[0];
+	     /* case 0: nothing left to add */
+   }
+   
+   mix(a,b,c);
+
+   /*-------------------------------------------- report the result */
+   return c;
+}
+
+
+void CopyFlattened( char* out, const char* in )
+{
+	// Convert a name like "John Stiles" to "JOHN STILES" or
+	// "Patty O'furniture" to "PATTY OFURNITURE".
+	char* start = out;
+	
+	while( *in == ' ' ) in++;
+	
+	for( ;; )
+	{
+		char c = *in++;
+		if( c == '\0' ) break;
+		
+		c = toupper(c);
+		if( c >= 'A' && c <= 'Z' || c == ' ' )
+		{
+			*out++ = c;
+		}
+	}
+	
+	while( (out > start) && (out[-1] == ' ') ) out--;
+	
+    *out++ = '\0';
+}
+
+
+MBoolean ValidateCode( const char* name, const char* key )
+{
+	// Convert the key (a jumble of letters like BFGJ-TVFF) into a 32-bit
+	// hex value. 
+	char*        unscrambled[8];
+	int          index;
+	unsigned int value = 0;
+	
+	if( strlen(key) != 9 ) return false;
+	
+	if( key[4] != '-' ) return false;
+	
+	unscrambled[0] = strchr( scrambleKey, toupper(key[0]) );
+	unscrambled[1] = strchr( scrambleKey, toupper(key[1]) );
+	unscrambled[2] = strchr( scrambleKey, toupper(key[2]) );
+	unscrambled[3] = strchr( scrambleKey, toupper(key[3]) );
+	unscrambled[4] = strchr( scrambleKey, toupper(key[5]) ); 
+	unscrambled[5] = strchr( scrambleKey, toupper(key[6]) );
+	unscrambled[6] = strchr( scrambleKey, toupper(key[7]) );
+	unscrambled[7] = strchr( scrambleKey, toupper(key[8]) );
+	
+	for( index=0; index<8; index++ )
+	{
+		if( unscrambled[index] == NULL ) return false;
+		value = (value << 4) | (unsigned int)(unscrambled[index] - scrambleKey);
+	}
+
+	// Get the flattened user name.
+	char flatName[256];
+	CopyFlattened( flatName, name );
+	
+	// Return true if any of the hash functions succeed.
+	// Note that the hashes and comparisons mix up the ordering of bogus 
+	// keys and valid keys to prevent early identification of bogus keys.
+	
+	int flatLength = strlen(flatName);
+	if( flatLength < 4 ) return false;
+	
+	unsigned int result0  = hash( (unsigned char*) flatName, flatLength, keylist[0] );
+	unsigned int result1  = hash( (unsigned char*) flatName, flatLength, keylist[1] );
+	unsigned int result2  = hash( (unsigned char*) flatName, flatLength, keylist[2] );
+	unsigned int result3  = hash( (unsigned char*) flatName, flatLength, keylist[3] );
+	unsigned int result4  = hash( (unsigned char*) flatName, flatLength, keylist[4] );
+	unsigned int result5  = hash( (unsigned char*) flatName, flatLength, keylist[5] );
+	unsigned int result6  = hash( (unsigned char*) flatName, flatLength, keylist[6] );
+	unsigned int result7  = hash( (unsigned char*) flatName, flatLength, keylist[7] );
+	unsigned int result8  = hash( (unsigned char*) flatName, flatLength, keylist[8] );
+	unsigned int result9  = hash( (unsigned char*) flatName, flatLength, keylist[9] );
+	unsigned int result10 = hash( (unsigned char*) flatName, flatLength, keylist[10] );
+	unsigned int result11 = hash( (unsigned char*) flatName, flatLength, keylist[11] );
+	unsigned int result12 = hash( (unsigned char*) flatName, flatLength, keylist[12] );
+
+	return (value == result0)  ||
+	       (value == result1)  ||
+	       (value == result2)  ||
+	       (value == result3)  ||
+	       (value == result4)  ||
+	       (value == result5)  ||
+	       (value == result6)  ||
+	       (value == result7)  ||
+	       (value == result8)  ||
+	       (value == result9)  ||
+	       (value == result10) ||
+	       (value == result11) ||	       
+	       (value == result12);	       
+
+/* A future version should become:
+
+	return ((value == result0)  && (flatName[0] == 'A' || flatName[0] == 'B')) ||
+	       ((value == result1)  && (flatName[0] == 'C' || flatName[0] == 'D')) ||
+	       ((value == result2)  && (flatName[0] == 'E' || flatName[0] == 'F')) ||
+	       ((value == result3)  && (flatName[0] == 'G' || flatName[0] == 'H')) ||
+	       ((value == result4)  && (flatName[0] == 'I' || flatName[0] == 'J')) ||
+	       ((value == result5)  && (flatName[0] == 'K' || flatName[0] == 'L')) ||
+	       ((value == result6)  && (flatName[0] == 'M' || flatName[0] == 'N')) ||
+	       ((value == result7)  && (flatName[0] == 'O' || flatName[0] == 'P')) ||
+	       ((value == result8)  && (flatName[0] == 'Q' || flatName[0] == 'R')) ||
+	       ((value == result9)  && (flatName[0] == 'S' || flatName[0] == 'T')) ||
+	       ((value == result10) && (flatName[0] == 'U' || flatName[0] == 'V')) ||
+	       ((value == result11) && (flatName[0] == 'W' || flatName[0] == 'X')) ||
+	       ((value == result12) && (flatName[0] == 'Y' || flatName[0] == 'Z'));
+
+*/
+}
+
+
--- /dev/null
+++ b/Source/RegAlgorithm.h
@@ -1,0 +1,19 @@
+///
+///  RegAlgorithm.h
+///
+
+
+
+#include "MTypes.h"
+
+extern const unsigned int keylist[13];
+extern const char* scrambleKey;
+
+
+// for code generator programs only!
+unsigned int hash( unsigned char* k, unsigned int length, unsigned int initval );
+void CopyFlattened( char* out, const char* in );
+
+// for client code...
+MBoolean ValidateCode( const char* name, const char* key );
+
--- /dev/null
+++ b/Source/RegGenerator.cpp
@@ -1,0 +1,82 @@
+///
+///  RegGenerator.cpp
+///
+///  Command-line tool for generating registration codes.
+///
+///  John Stiles, 2002/11/1
+///
+
+
+#include <stdio.h>
+#include "RegAlgorithm.h"
+
+
+static void GenerateCode( const char* name, char* outCode )
+{
+	int          pool;
+	char         flatName[256];
+	unsigned int value;
+	
+	CopyFlattened( flatName, name );
+	
+	switch( flatName[0] )
+	{
+		case 'A': case 'B': pool = 0;  break;
+		case 'C': case 'D': pool = 1;  break;
+		case 'E': case 'F': pool = 2;  break;
+		case 'G': case 'H': pool = 3;  break;
+		case 'I': case 'J': pool = 4;  break;
+		case 'K': case 'L': pool = 5;  break;
+		case 'M': case 'N': pool = 6;  break;
+		case 'O': case 'P': pool = 7;  break;
+		case 'Q': case 'R': pool = 8;  break;
+		case 'S': case 'T': pool = 9;  break;
+		case 'U': case 'V': pool = 10; break;
+		case 'W': case 'X': pool = 11; break;
+		case 'Y': case 'Z': pool = 12; break;
+	}
+	
+	int flatLength = strlen(flatName);
+    
+	value = hash( (unsigned char*) flatName, flatLength, keylist[pool] );
+
+	sprintf( outCode, "%c%c%c%c-%c%c%c%c",
+						scrambleKey[ (value >> 28) & 0xf ],
+						scrambleKey[ (value >> 24) & 0xf ],
+						scrambleKey[ (value >> 20) & 0xf ],
+						scrambleKey[ (value >> 16) & 0xf ],
+						scrambleKey[ (value >> 12) & 0xf ],
+						scrambleKey[ (value >>  8) & 0xf ],
+						scrambleKey[ (value >>  4) & 0xf ],
+						scrambleKey[ (value >>  0) & 0xf ]  );						
+}
+
+
+int main( int argc, char* argv[] )
+{
+	char code[64];
+	
+	if( argc != 2 )
+	{
+		fprintf( stderr, "usage: crisisgen \"User Name\"\n" );
+		exit(1);
+	}
+	
+	if( strlen(argv[1]) < 4 )
+    {
+        fprintf( stderr, "ERROR: name too short\n" );
+        exit(1);
+    }
+    
+	GenerateCode( argv[1], code );
+ 
+    if( !ValidateCode( argv[1], code ) )
+    {
+        fprintf( stderr, "ERROR: code generation failure for name \"%s\"!", argv[1] );
+        exit(1);
+    }   
+    
+	printf( "%s\n", code );
+	
+	return 0;
+}
--- /dev/null
+++ b/Source/SDLU.cpp
@@ -1,0 +1,880 @@
+///
+///  SDLU.c
+///
+///  SDL utilities.
+///
+///  John Stiles, 2002/10/12
+///
+
+#include "SDL.h"
+#include "SDLU.h"
+#include "gameticks.h"
+#include "music.h"
+
+#include "main.h" // for Error
+
+
+// for acquiresurface
+const int           k_acquireMax = 10;
+static int          s_acquireHead = -1;
+static SDL_Surface* s_acquireList[k_acquireMax];
+
+// for button and getmouse
+static int          s_mouseButton;
+static MPoint       s_mousePosition;
+
+// for event loop
+static MBoolean     s_isForeground = true;
+ 
+// for checktyping
+static MBoolean     s_interestedInTyping = false;
+static char         s_keyBufferASCII[16] = { 0 };
+static SDLKey       s_keyBufferSDL[16]   = { (SDLKey) 0};
+static int          s_keyBufferPullFrom = 0;
+static int          s_keyBufferPutAt = 0;
+static int          s_keyBufferFilled = 0;
+
+static void	SDLUi_Blit8BitTo1Bit( SDL_Surface* surface8, SDL_Rect* rect8,
+                                  SDL_Surface* surface1, SDL_Rect* rect1  )
+{
+	// NOTE: for now this copy assumes that we're copying the whole thing.
+	// That's probably true for everything I'm doing. If it turns out that
+	// I need partial 8->1 copies, this won't be too hard to fix.
+	
+	int          x, y, across, down;
+	SDL_Color*   palette8;
+
+	rect8; // is unused for now
+	
+//	ASSERTN( surface8->format->BitsPerPixel == 8, surface8->format->BitsPerPixel );
+//	ASSERTN( surface1->format->BitsPerPixel == 1, surface8->format->BitsPerPixel );
+//	ASSERT( rect8->w == rect1->w );
+//	ASSERT( rect8->h == rect1->h );
+	
+	palette8 = surface8->format->palette->colors;
+	down     = rect1->h;
+	across   = (rect1->w + 7) & ~7;
+	
+	for( y=0; y<down; y++ )
+	{
+		unsigned char* src = (unsigned char*) surface8->pixels + (y * surface8->pitch);
+		unsigned char* dst = (unsigned char*) surface1->pixels + (y * surface1->pitch);
+		
+		for( x=0; x<across; x+=8 )
+		{
+			*dst = (palette8[src[0]].r? 0: 0x80) |
+			       (palette8[src[1]].r? 0: 0x40) |
+			       (palette8[src[2]].r? 0: 0x20) |
+			       (palette8[src[3]].r? 0: 0x10) |
+			       (palette8[src[4]].r? 0: 0x08) |
+			       (palette8[src[5]].r? 0: 0x04) |
+			       (palette8[src[6]].r? 0: 0x02) |
+			       (palette8[src[7]].r? 0: 0x01)   ;
+			
+			dst += 1;
+			src += 8;
+		}
+	}
+}
+
+
+static void	SDLUi_Blit15BitTo16Bit( SDL_Surface* surface15, SDL_Rect* rect15,
+                                    SDL_Surface* surface16, SDL_Rect* rect16  )
+{
+	int             x, y, srcPitch, dstPitch;
+	unsigned short* src;
+	unsigned short* dst;
+	unsigned short  work;
+	int             rect15x = rect15->x, rect15y = rect15->y, rect15w = rect15->w, rect15h = rect15->h; 
+	int             rect16x = rect16->x, rect16y = rect16->y, rect16w = rect16->w, rect16h = rect16->h; 
+	int             surface15w = surface15->w, surface15h = surface15->h;
+	int             surface16w = surface16->w, surface16h = surface16->h;
+	
+	// Clip.
+	if( rect15x < 0 )      { rect15w += rect15x; rect16w += rect15x; rect16x -= rect15x; rect15x = 0; }
+	if( rect15y < 0 )      { rect15h += rect15y; rect16h += rect15y; rect16y -= rect15y; rect15y = 0; }
+	if( (rect15x + rect15w) > surface15w ) { rect15w = surface15w - rect15x; }
+	if( (rect15y + rect15h) > surface15h ) { rect15h = surface15h - rect15y; }
+
+	if( rect16x < 0 )      { rect16w += rect16x; rect15w += rect16x; rect15x -= rect16x; rect16x = 0; }
+	if( rect16y < 0 )      { rect16h += rect16y; rect15h += rect16y; rect15y -= rect16y; rect16y = 0; }
+	if( (rect16x + rect16w) > surface16w ) { rect16w = surface16w - rect16x; }
+	if( (rect16y + rect16h) > surface16h ) { rect16h = surface16h - rect16y; }
+
+	// SDL changes the destination rectangle to the actual copied bounds. We need to do the same.
+	if( rect16w <= 0 || rect16h <= 0 ) 
+	{
+		rect16->w = 0; rect16->h = 0;
+		return;
+	}
+
+	rect16->x = rect16x; rect16->y = rect16y; rect16->w = rect16w; rect16->h = rect16h; 
+
+	// Blit.
+	srcPitch = surface15->pitch;
+	dstPitch = surface16->pitch;
+	
+	src = (unsigned short*) ( ((unsigned char*) surface15->pixels) + (rect15x * 2) + (rect15y * srcPitch) );
+	dst = (unsigned short*) ( ((unsigned char*) surface16->pixels) + (rect16x * 2) + (rect16y * dstPitch) );
+	
+	for( y=0; y<rect16h; y++ )
+	{
+		for( x=0; x<rect16w; x++ )
+		{
+			work = src[x];
+			work = (work << 1) & 0xFFC0 |
+			       (work >> 4) & 0x0020 |
+			       (work     ) & 0x001F;
+			       
+			dst[x] = work;
+		}
+		
+		src = (unsigned short*) ( ((unsigned char*) src) + srcPitch );
+		dst = (unsigned short*) ( ((unsigned char*) dst) + dstPitch );
+	}
+}
+
+
+static void	SDLUi_Blit16BitTo15Bit( SDL_Surface* surface16, SDL_Rect* rect16,
+                                    SDL_Surface* surface15, SDL_Rect* rect15  )
+{
+	int             x, y, srcPitch, dstPitch;
+	unsigned short* src;
+	unsigned short* dst;
+	unsigned short  work;
+	int             rect15x = rect15->x, rect15y = rect15->y, rect15w = rect15->w, rect15h = rect15->h; 
+	int             rect16x = rect16->x, rect16y = rect16->y, rect16w = rect16->w, rect16h = rect16->h; 
+	int             surface15w = surface15->w, surface15h = surface15->h;
+	int             surface16w = surface16->w, surface16h = surface16->h;
+
+	// Clip.
+	if( rect15x < 0 )      { rect15w += rect15x; rect16w += rect15x; rect16x -= rect15x; rect15x = 0; }
+	if( rect15y < 0 )      { rect15h += rect15y; rect16h += rect15y; rect16y -= rect15y; rect15y = 0; }
+	if( (rect15x + rect15w) > surface15w ) { rect15w = surface15w - rect15x; }
+	if( (rect15y + rect15h) > surface15h ) { rect15h = surface15h - rect15y; }
+
+	if( rect16x < 0 )      { rect16w += rect16x; rect15w += rect16x; rect15x -= rect16x; rect16x = 0; }
+	if( rect16y < 0 )      { rect16h += rect16y; rect15h += rect16y; rect15y -= rect16y; rect16y = 0; }
+	if( (rect16x + rect16w) > surface16w ) { rect16w = surface16w - rect16x; }
+	if( (rect16y + rect16h) > surface16h ) { rect16h = surface16h - rect16y; }
+
+	// SDL changes the destination rectangle to the actual copied bounds. We need to do the same.
+	if( rect15w <= 0 || rect15h <= 0 ) 
+	{
+		rect15->w = 0; rect15->h = 0;
+		return;
+	}
+
+	rect15->x = rect15x; rect15->y = rect15y; rect15->w = rect15w; rect15->h = rect15h; 
+	
+	// Blit.
+	srcPitch = surface16->pitch;
+	dstPitch = surface15->pitch;
+	
+	src = (unsigned short*) ( ((unsigned char*) surface16->pixels) + (rect16x * 2) + (rect16y * srcPitch) );
+	dst = (unsigned short*) ( ((unsigned char*) surface15->pixels) + (rect15x * 2) + (rect15y * dstPitch) );
+	
+	for( y=0; y<rect15h; y++ )
+	{
+		for( x=0; x<rect15w; x++ )
+		{
+			work = src[x];
+			work = (work >> 1) & 0x7FE0 |
+			       (work     ) & 0x001F;
+			       
+			dst[x] = work;
+		}
+		
+		src = (unsigned short*) ( ((unsigned char*) src) + srcPitch );
+		dst = (unsigned short*) ( ((unsigned char*) dst) + dstPitch );
+	}
+}
+
+
+static void	SDLUi_Blit24BitTo15BitHQ( SDL_Surface* surface24, SDL_Rect* rect24,
+                                      SDL_Surface* surface15, SDL_Rect* rect15  )
+{
+	int             x, y, x3, srcPitch, dstPitch;
+	unsigned char*  src;
+	unsigned short* dst;
+	unsigned long   work;
+	int             rect15x = rect15->x, rect15y = rect15->y, rect15w = rect15->w, rect15h = rect15->h, rect15wE; 
+	int             rect24x = rect24->x, rect24y = rect24->y, rect24w = rect24->w, rect24h = rect24->h; 
+	int             surface15w = surface15->w, surface15h = surface15->h;
+	int             surface24w = surface24->w, surface24h = surface24->h;
+
+	const unsigned char* aPixels = NULL;
+	const unsigned char* bPixels = NULL;
+	const unsigned char* swapPixels = NULL;
+	
+	const unsigned char copyPixels[256] = 
+	{
+		0,  0,  0,  0,  0,  0,  0,  0,  
+		1,  1,  1,  1,  1,  1,  1,  1,  
+		2,  2,  2,  2,  2,  2,  2,  2,  
+		3,  3,  3,  3,  3,  3,  3,  3,  
+		4,  4,  4,  4,  4,  4,  4,  4,  
+		5,  5,  5,  5,  5,  5,  5,  5,  
+		6,  6,  6,  6,  6,  6,  6,  6,  
+		7,  7,  7,  7,  7,  7,  7,  7,  
+		8,  8,  8,  8,  8,  8,  8,  8,  
+		9,  9,  9,  9,  9,  9,  9,  9,  
+		10, 10, 10, 10, 10, 10, 10, 10, 
+		11, 11, 11, 11, 11, 11, 11, 11, 
+		12, 12, 12, 12, 12, 12, 12, 12, 
+		13, 13, 13, 13, 13, 13, 13, 13, 
+		14, 14, 14, 14, 14, 14, 14, 14, 
+		15, 15, 15, 15, 15, 15, 15, 15, 
+		16, 16, 16, 16, 16, 16, 16, 16, 
+		17, 17, 17, 17, 17, 17, 17, 17, 
+		18, 18, 18, 18, 18, 18, 18, 18, 
+		19, 19, 19, 19, 19, 19, 19, 19,  
+		20, 20, 20, 20, 20, 20, 20, 20, 
+		21, 21, 21, 21, 21, 21, 21, 21, 
+		22, 22, 22, 22, 22, 22, 22, 22, 
+		23, 23, 23, 23, 23, 23, 23, 23, 
+		24, 24, 24, 24, 24, 24, 24, 24, 
+		25, 25, 25, 25, 25, 25, 25, 25, 
+		26, 26, 26, 26, 26, 26, 26, 26, 
+		27, 27, 27, 27, 27, 27, 27, 27, 
+		28, 28, 28, 28, 28, 28, 28, 28, 
+		29, 29, 29, 29, 29, 29, 29, 29,  
+		30, 30, 30, 30, 30, 30, 30, 30, 
+		31, 31, 31, 31, 31, 31, 31, 31 
+	};
+	
+	const unsigned char ditherPixels[256] = 
+	{
+		0,  0,  0,  0,  1,  1,  1,  1,  
+		1,  1,  1,  1,  2,  2,  2,  2,  
+		2,  2,  2,  2,  3,  3,  3,  3,  
+		3,  3,  3,  3,  4,  4,  4,  4,  
+		4,  4,  4,  4,  5,  5,  5,  5,  
+		5,  5,  5,  5,  6,  6,  6,  6,  
+		6,  6,  6,  6,  7,  7,  7,  7,  
+		7,  7,  7,  7,  8,  8,  8,  8,  
+		8,  8,  8,  8,  9,  9,  9,  9,  
+		9,  9,  9,  9,  10, 10, 10, 10, 
+		10, 10, 10, 10, 11, 11, 11, 11, 
+		11, 11, 11, 11, 12, 12, 12, 12, 
+		12, 12, 12, 12, 13, 13, 13, 13, 
+		13, 13, 13, 13, 14, 14, 14, 14, 
+		14, 14, 14, 14, 15, 15, 15, 15, 
+		15, 15, 15, 15, 16, 16, 16, 16, 
+		16, 16, 16, 16, 17, 17, 17, 17, 
+		17, 17, 17, 17, 18, 18, 18, 18, 
+		18, 18, 18, 18, 19, 19, 19, 19, 
+		19, 19, 19, 19, 20, 20, 20, 20,  
+		20, 20, 20, 20, 21, 21, 21, 21, 
+		21, 21, 21, 21, 22, 22, 22, 22, 
+		22, 22, 22, 22, 23, 23, 23, 23, 
+		23, 23, 23, 23, 24, 24, 24, 24, 
+		24, 24, 24, 24, 25, 25, 25, 25, 
+		25, 25, 25, 25, 26, 26, 26, 26, 
+		26, 26, 26, 26, 27, 27, 27, 27, 
+		27, 27, 27, 27, 28, 28, 28, 28, 
+		28, 28, 28, 28, 29, 29, 29, 29, 
+		29, 29, 29, 29, 30, 30, 30, 30,  
+		30, 30, 30, 30, 31, 31, 31, 31, 
+		31, 31, 31, 31, 31, 31, 31, 31 
+	};
+	
+	// Clip.
+	if( rect15x < 0 )      { rect15w += rect15x; rect24w += rect15x; rect24x -= rect15x; rect15x = 0; }
+	if( rect15y < 0 )      { rect15h += rect15y; rect24h += rect15y; rect24y -= rect15y; rect15y = 0; }
+	if( (rect15x + rect15w) > surface15w ) { rect15w = surface15w - rect15x; }
+	if( (rect15y + rect15h) > surface15h ) { rect15h = surface15h - rect15y; }
+
+	if( rect24x < 0 )      { rect24w += rect24x; rect15w += rect24x; rect15x -= rect24x; rect24x = 0; }
+	if( rect24y < 0 )      { rect24h += rect24y; rect15h += rect24y; rect15y -= rect24y; rect24y = 0; }
+	if( (rect24x + rect24w) > surface24w ) { rect24w = surface24w - rect24x; }
+	if( (rect24y + rect24h) > surface24h ) { rect24h = surface24h - rect24y; }
+
+	// SDL changes the destination rectangle to the actual copied bounds. We need to do the same.
+	if( rect15w <= 0 || rect15h <= 0 ) 
+	{
+		rect15->w = 0; rect15->h = 0;
+		return;
+	}
+
+	rect15->x = rect15x; rect15->y = rect15y; rect15->w = rect15w; rect15->h = rect15h; 
+	
+	// Blit.
+	srcPitch = surface24->pitch;
+	dstPitch = surface15->pitch;
+	
+	src      = (unsigned char*)  ( ((unsigned char*) surface24->pixels) + (rect24x * 3) + (rect24y * srcPitch) );
+	dst      = (unsigned short*) ( ((unsigned char*) surface15->pixels) + (rect15x * 2) + (rect15y * dstPitch) );
+	rect15wE = rect15w & ~1;
+	
+	for( y=0; y<rect15h; y++ )
+	{
+		if( y & 1 )
+		{
+			aPixels = copyPixels;
+			bPixels = ditherPixels;
+		}
+		else
+		{
+			aPixels = ditherPixels;
+			bPixels = copyPixels;
+		}
+		
+		x=0; x3=0;
+		while( x < rect15wE )
+		{
+			work  = (aPixels[src[x3+0]] << 10) |
+			        (aPixels[src[x3+1]] <<  5) |
+			        (aPixels[src[x3+2]]      );
+			       
+			dst[x] = work;
+			
+			x++; x3+=3;
+			
+			work  = (bPixels[src[x3+0]] << 10) |
+			        (bPixels[src[x3+1]] <<  5) |
+			        (bPixels[src[x3+2]]      );
+			       
+			dst[x] = work;
+
+			x++; x3+=3;
+		}
+		
+		if( rect15w & 1 )
+		{
+			work  = (aPixels[src[x3+0]] << 10) |
+			        (aPixels[src[x3+1]] <<  5) |
+			        (aPixels[src[x3+2]]      );
+			       
+			dst[x] = work;
+		}
+		
+		src = (unsigned char*)  ( ((unsigned char*) src) + srcPitch );
+		dst = (unsigned short*) ( ((unsigned char*) dst) + dstPitch );
+	}
+}
+
+
+static void SDLUi_SetGrayscaleColors( SDL_Surface* surface )
+{
+	SDL_Color  grayscalePalette[256];
+	int        index;
+	
+	for( index=0; index<256; index++ )
+	{
+		grayscalePalette[index].r = 
+		grayscalePalette[index].g = 
+		grayscalePalette[index].b = 255 - index; 
+		grayscalePalette[index].unused = 0; 
+	}
+	
+	SDL_SetColors( surface, grayscalePalette, 0, 256 );
+}
+
+
+/*
+GWorldPtr SDLU_SurfaceToGWorld( SDL_Surface* surface )
+{
+	OSErr     err;
+	int       depth = 0;
+	GWorldPtr gworld;
+	
+	switch( surface->format->BitsPerPixel )
+	{
+		case 1:
+			depth = 1;
+			break;
+		
+		case 15:
+			depth = 16;
+			break;
+		
+		default:
+			Error( errUnknown, "\pSDLU_SurfaceToGWorld: depth" );
+			break;
+	}
+	
+	MRect mRect = { 0, 0, surface->h, surface->w };
+	
+	err = NewGWorldFromPtr( &gworld, depth, &mRect, NULL, NULL, 0, (Ptr) surface->pixels, surface->pitch );
+	if( err != noErr )
+	{
+		Error( errUnknown, "\pSDLU_SurfaceToGWorld: NewGWorldFromPtr" );
+	}
+	
+	return gworld;
+}
+*/
+
+
+/*
+void SDLU_DumpSurface( SDL_Surface* surface, const char* filenamePre, const char* filenamePost )
+{
+#if TARGET_API_MAC_CARBON
+	OSErr     err;
+	int       depth = 0;
+	GWorldPtr gworld;
+	
+	switch( surface->format->BitsPerPixel )
+	{
+		case 1:
+			depth = 1;
+			break;
+		
+		case 15:
+			depth = 16;
+			break;
+
+		case 16:
+			depth = 16;
+			break;
+		
+		default:
+			Error( "SDLU_DumpSurface: depth" );
+			break;
+	}
+	
+	Rect mRect = { 0, 0, surface->h, surface->w };
+	
+	err = NewGWorldFromPtr( &gworld, depth, &mRect, NULL, NULL, 0, (Ptr) surface->pixels, surface->pitch );
+	if( err != noErr )
+	{
+		Error( "SDLU_DumpSurface: NewGWorldFromPtr" );
+	}
+
+	CGrafPtr port; GDHandle gdh;
+	GetGWorld( &port, &gdh );
+	SetGWorld( gworld, NULL );
+	PicHandle thePicture = OpenPicture( &mRect );
+	CopyBits( GetPortBitMapForCopyBits(gworld), GetPortBitMapForCopyBits(gworld),
+				&mRect,	&mRect,
+				srcCopy, nil );
+	ClosePicture( );
+	SetGWorld( port, gdh );
+	
+	FSSpec filespec = {0};
+	static int incr = 0;
+	filespec.name[0] = sprintf( (char*) &filespec.name[1], "%s%d%s.jpg", filenamePre, incr++, filenamePost );
+	
+	OSType filetype = kQTFileTypeJPEG;
+	OSType filecreator = 'TVOD';
+	int    filescriptcode = smSystemScript;
+	
+	// see: http://developer.apple.com/quicktime/icefloe/dispatch014.html
+	{
+	    Handle h;
+	    OSErr err;
+	    GraphicsImportComponent gi = 0;
+	    
+	    // Convert the picture handle into a PICT file (still in a handle) 
+	    // by adding a 512-byte header to the start.
+	    h = NewHandleClear(512);
+	    err = MemError();
+	    if(err) goto bail;
+	    err = HandAndHand((Handle)thePicture,h);
+	    
+	    err = OpenADefaultComponent(
+	                GraphicsImporterComponentType,
+	                kQTFileTypePicture,
+	                &gi);
+	    if(err) goto bail;
+	    
+	    err = GraphicsImportSetDataHandle(gi, h);
+	    if(err) goto bail;
+	    
+	    err = GraphicsImportExportImageFile(
+	                gi, 
+	                filetype, 
+	                filecreator, 
+	                &filespec, 
+	                filescriptcode);
+	    if(err) goto bail;
+	    
+	bail:
+	    if(gi) CloseComponent(gi);
+	    if(h) DisposeHandle(h);
+	}
+	
+	DisposeHandle( (Handle) thePicture );
+	DisposeGWorld( gworld );
+#endif
+}
+*/
+
+
+SDL_Rect* SDLU_MRectToSDLRect( const MRect* in, SDL_Rect* out )
+{
+	int t = in->top, l = in->left, b = in->bottom, r = in->right;
+	
+	out->x = l;
+	out->y = t;
+	out->w = r - l;
+	out->h = b - t; 
+	
+	return out;
+}
+
+
+MRect* SDLU_SDLRectToMRect( const SDL_Rect* in, MRect* out )
+{
+	int x = in->x, y = in->y, w = in->w, h = in->h;
+	
+	out->top    = y;
+	out->left   = x;
+	out->bottom = y + h;
+	out->right  = x + w;
+	
+	return out; 
+}
+
+
+int SDLU_BlitSurface( SDL_Surface* src, SDL_Rect* srcrect,
+			          SDL_Surface* dst, SDL_Rect* dstrect  )
+{
+	if( src->format->BitsPerPixel == 8 && dst->format->BitsPerPixel == 1 )
+	{
+		// SDL BUG!! SDL cannot blit 8-bit to 1-bit surfaces.
+		SDLUi_Blit8BitTo1Bit( src, srcrect,
+		                      dst, dstrect  );
+		return 0;
+	}
+
+	// SDL PERFORMANCE: SDL sucks at blitting 15->16 and 16->15.
+	// Use hand-rolled fast blitters to solve this.
+	if( src->format->BitsPerPixel == 15 && dst->format->BitsPerPixel == 16 )
+	{
+		SDLUi_Blit15BitTo16Bit( src, srcrect,
+		                        dst, dstrect  );
+		return 0;	
+	}
+	
+	
+	if( src->format->BitsPerPixel == 16 && dst->format->BitsPerPixel == 15 )
+	{
+		SDLUi_Blit16BitTo15Bit( src, srcrect,
+		                        dst, dstrect  );
+		return 0;	
+	}
+
+	// Let SDL handle this.
+	return SDL_BlitSurface( src, srcrect,
+	                        dst, dstrect  );
+}
+
+
+int SDLU_BlitSurfaceHQ( SDL_Surface* src, SDL_Rect* srcrect,
+			            SDL_Surface* dst, SDL_Rect* dstrect  )
+{
+	// QUALITY: Use a hand-rolled 24->15 dithering blitter.
+	if( src->format->BitsPerPixel == 24 && dst->format->BitsPerPixel == 15 )
+	{
+		SDLUi_Blit24BitTo15BitHQ( src, srcrect,
+		                          dst, dstrect  );
+		return 0;	
+	}
+	
+	// Let SDL handle this.
+	return SDLU_BlitSurface( src, srcrect,
+	                         dst, dstrect  );
+}
+
+
+void SDLU_GetPixel(	SDL_Surface* surface, int x, int y, SDL_Color* pixel )
+{
+	unsigned long px;
+	unsigned char* ptr;
+	
+	switch( surface->format->BytesPerPixel )
+	{
+		case 1:
+			ptr = (unsigned char*)surface->pixels + (y * surface->pitch) + (x);
+			px = *(unsigned char*) ptr;
+			break;
+		
+		case 2:
+			ptr = (unsigned char*)surface->pixels + (y * surface->pitch) + (x * 2);
+			px = *(unsigned short*) ptr;
+			break;
+
+		case 4:
+			ptr = (unsigned char*)surface->pixels + (y * surface->pitch) + (x * 4);
+			px = *(unsigned long*) ptr;
+			break;
+	}
+	
+	return SDL_GetRGB( px, surface->format, &pixel->r, &pixel->g, &pixel->b );
+}
+
+
+void SDLU_ChangeSurfaceDepth( SDL_Surface** surface, int depth )
+{
+	SDL_Surface* newSurface;
+
+	newSurface = SDLU_InitSurface( &surface[0]->clip_rect, depth );
+	
+	SDLU_BlitSurfaceHQ( *surface,    &surface[0]->clip_rect,
+	                     newSurface, &newSurface->clip_rect  );
+			
+	SDL_FreeSurface( *surface );
+	
+	*surface = newSurface;
+}
+
+
+SDL_Surface* SDLU_InitSurface( SDL_Rect* rect, int depth )
+{
+	SDL_Surface*    surface = NULL;
+	SDL_Color       k_oneBitPalette[2] = { { 0xFF, 0xFF, 0xFF, 0x00 },
+	                                       { 0x00, 0x00, 0x00, 0x00 }  };
+	
+	switch( depth )
+	{
+		case 16:
+			surface = SDL_CreateRGBSurface( 
+							SDL_SWSURFACE, 
+							rect->w, 
+							rect->h, 
+							15, 
+							0x7C00, 0x03E0, 0x001F, 0x0000 );
+			break;
+		
+		case 8:
+			surface = SDL_CreateRGBSurface( 
+							SDL_SWSURFACE, 
+							rect->w, 
+							rect->h, 
+							8, 
+							0, 0, 0, 0 );
+
+			SDLUi_SetGrayscaleColors( surface );
+			break;
+		
+		case 1:
+			surface = SDL_CreateRGBSurface( 
+							SDL_SWSURFACE, 
+							rect->w, 
+							rect->h, 
+							1, 
+							0, 0, 0, 0 );
+			
+			SDL_SetColors( surface, k_oneBitPalette, 0, 2 );
+			break;
+	}					
+	
+	if( surface == NULL )
+	{
+		Error( "SDLU_InitSurface: SDL_CreateRGBSurface" );
+		return NULL;
+	}
+	
+	// SDL BUG!! SDL_FillRect blows up with 1-depth surfaces. WORKAROUND: don't auto-clear them. Seems OK.
+	//           (Next step is always to copy a PNG into them.)
+	if( depth >= 8 )
+		SDL_FillRect( surface, rect, SDL_MapRGB( surface->format, 0xFF, 0xFF, 0xFF ) );
+	
+	return surface;
+}
+
+
+void SDLU_BlitFrontSurface( SDL_Surface* source, SDL_Rect* sourceSDLRect, SDL_Rect* destSDLRect )
+{
+	extern SDL_Surface* frontSurface;
+	SDLU_BlitSurface( source,       sourceSDLRect,
+	                  frontSurface, destSDLRect );
+	
+	SDL_UpdateRect( frontSurface, destSDLRect->x, destSDLRect->y, destSDLRect->w, destSDLRect->h );
+}
+
+
+void SDLU_SetBrightness( float b )
+{
+	Uint16 table[256];
+	int    index;
+	
+	for( index=0; index<256; index++ )
+	{
+		table[index] = (int)(index * b * 257.0f); // 255 * 257 = 65535
+	}
+	
+	SDL_SetGammaRamp( table, table, table );
+}
+
+void SDLU_Yield()
+{
+    SDL_Delay( 2 );
+    SDL_PumpEvents();
+}
+
+void SDLU_PumpEvents()
+{
+	static unsigned long lastPump = 0;
+	unsigned long time = MTickCount();
+	
+	if( lastPump != time )
+	{
+        SDL_Event evt;
+        while( SDL_PollEvent( &evt ) ) { }
+		lastPump = time;
+	}
+}
+
+
+MBoolean SDLU_IsForeground()
+{
+    return s_isForeground;
+}
+
+
+int SDLU_EventFilter( const SDL_Event *event )
+{
+	// Put keydowns in a buffer
+	if( event->type == SDL_KEYDOWN )
+	{
+		if(    s_interestedInTyping
+		    && event->key.keysym.unicode <= 127 
+		    && s_keyBufferFilled < sizeof(s_keyBufferASCII) )
+		{
+			s_keyBufferFilled++;
+			s_keyBufferASCII[s_keyBufferPutAt] = event->key.keysym.unicode;
+			s_keyBufferSDL  [s_keyBufferPutAt] = event->key.keysym.sym;
+			s_keyBufferPutAt = (s_keyBufferPutAt + 1) % sizeof(s_keyBufferASCII);
+		}
+
+		if(    (event->key.keysym.sym == SDLK_F4)
+		    && (event->key.keysym.mod & (KMOD_LALT | KMOD_RALT)) )
+		{
+			finished = true;
+		}
+		
+		return 0;
+	}
+	
+	// Get mouse state
+	if( event->type == SDL_MOUSEBUTTONDOWN )
+	{
+		if( event->button.button == SDL_BUTTON_LEFT )
+			s_mouseButton = true;
+		
+		s_mousePosition.v = event->button.y;
+		s_mousePosition.h = event->button.x;
+		return 0;
+	}
+	
+	if( event->type == SDL_MOUSEBUTTONUP )
+	{
+		if( event->button.button == SDL_BUTTON_LEFT )
+			s_mouseButton = false;
+	
+		s_mousePosition.v = event->button.y;
+		s_mousePosition.h = event->button.x;
+		return 0;
+	}
+
+	if( event->type == SDL_MOUSEMOTION )
+	{
+		s_mousePosition.v = event->motion.y;
+		s_mousePosition.h = event->motion.x;
+		s_mouseButton = event->motion.state & SDL_BUTTON(1);
+		return 0;
+	}
+ 
+	if( event->type == SDL_QUIT ) 
+	{
+		finished = true;
+		return 0;
+	}
+	
+	// Handle gaining and losing focus (kind of cheesy)
+	if( event->type == SDL_ACTIVEEVENT && (event->active.state & SDL_APPINPUTFOCUS) )
+	{
+		if( !event->active.gain && s_isForeground )
+		{
+			FreezeGameTickCount();
+			EnableMusic(false);
+            s_isForeground = false;
+		}
+		else if( event->active.gain && !s_isForeground )
+		{
+			UnfreezeGameTickCount();
+			EnableMusic(musicOn);
+            s_isForeground = true;
+
+			DoFullRepaint();
+		}
+	}
+	
+	// We never poll for events, we just process them here, so discard everything.
+	return 0;
+}
+
+
+void SDLU_StartWatchingTyping()
+{
+	s_interestedInTyping = true;
+	s_keyBufferFilled = s_keyBufferPullFrom = s_keyBufferPutAt = 0;
+	SDL_EnableUNICODE( 1 );
+	SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL );
+}
+
+
+void SDLU_StopWatchingTyping()
+{
+	s_interestedInTyping = false;
+	SDL_EnableUNICODE( 0 );
+	SDL_EnableKeyRepeat( 0, 0 );
+}
+
+
+MBoolean SDLU_CheckTyping( char* ascii, SDLKey* sdl )
+{
+	if( s_keyBufferFilled > 0 )
+	{
+		*ascii = s_keyBufferASCII[s_keyBufferPullFrom];
+		*sdl   = s_keyBufferSDL  [s_keyBufferPullFrom];
+		s_keyBufferPullFrom = (s_keyBufferPullFrom + 1) % sizeof(s_keyBufferASCII);
+		s_keyBufferFilled--;
+		return true;
+	}
+	
+	*ascii = '\0';
+	*sdl   = SDLK_UNKNOWN;
+	return false;
+}
+
+
+void SDLU_GetMouse( MPoint* pt )
+{
+	SDLU_PumpEvents();
+	*pt = s_mousePosition;
+}
+
+
+int SDLU_Button()
+{
+	SDLU_PumpEvents();
+	return s_mouseButton;
+}
+
+
+void SDLU_AcquireSurface( SDL_Surface* surface )
+{
+	s_acquireList[++s_acquireHead] = surface;
+}
+
+
+SDL_Surface* SDLU_GetCurrentSurface()
+{	
+	return s_acquireList[s_acquireHead];
+}
+
+
+void SDLU_ReleaseSurface( SDL_Surface* surface )
+{
+	if( s_acquireList[s_acquireHead] != surface )
+		Error( "SDLU_ReleaseSurface: out of order" );
+		
+	if( s_acquireHead < 0 )
+		Error( "SDLU_ReleaseSurface: underflow" );
+		
+	s_acquireHead--;
+}
--- /dev/null
+++ b/Source/SDLU.h
@@ -1,0 +1,37 @@
+///
+///  SDLU.h
+///
+
+
+#ifndef __SDLU__
+#define __SDLU__
+
+
+#include "MTypes.h"
+
+
+//GWorldPtr  SDLU_SurfaceToGWorld( SDL_Surface* surface );
+SDL_Rect*    SDLU_MRectToSDLRect( const MRect* in, SDL_Rect* out );
+MRect*       SDLU_SDLRectToMRect( const SDL_Rect* in, MRect* out );
+int          SDLU_BlitSurface( SDL_Surface* src, SDL_Rect* srcrect, SDL_Surface* dst, SDL_Rect* dstrect );
+void         SDLU_GetPixel( SDL_Surface* surface, int x, int y, SDL_Color* pixel );
+void         SDLU_ChangeSurfaceDepth( SDL_Surface** surface, int depth );
+SDL_Surface* SDLU_InitSurface( SDL_Rect* rect, int depth );
+void         SDLU_BlitFrontSurface( SDL_Surface* source, SDL_Rect* sourceSDLRect, SDL_Rect* destSDLRect );
+void         SDLU_SetBrightness( float b );
+void         SDLU_AcquireSurface( SDL_Surface* surface );
+SDL_Surface* SDLU_GetCurrentSurface();
+void         SDLU_ReleaseSurface( SDL_Surface* surface );
+void         SDLU_GetMouse( MPoint* pt );
+int          SDLU_Button();
+void         SDLU_Yield();
+void         SDLU_PumpEvents();
+int          SDLU_EventFilter( const SDL_Event *event );
+void         SDLU_StartWatchingTyping();
+void         SDLU_StopWatchingTyping();
+MBoolean     SDLU_CheckTyping( char* ascii, SDLKey* sdl );
+int          SDLU_BlitSurfaceHQ( SDL_Surface* src, SDL_Rect* srcrect, SDL_Surface* dst, SDL_Rect* dstrect );
+MBoolean     SDLU_IsForeground();
+
+
+#endif
--- /dev/null
+++ b/Source/blitter.cpp
@@ -1,0 +1,882 @@
+// blitter.c
+
+#include "SDL.h"
+#include "SDLU.h"
+#include "SDL_endian.h"
+
+#include "main.h"
+#include "gworld.h"
+#include "blitter.h"
+#include "font.h"
+#include "level.h"
+#include "graphics.h"
+
+MBoolean update[2][kGridAcross][kGridDown];
+MBoolean refresh[2];
+
+void InitBlitter( void )
+{
+	int player, x, y;
+
+	for( player=0; player<=1; player++ )
+	{
+		refresh[player] = false;
+		
+		for( x=0; x<kGridAcross; x++ )
+		{
+			for( y=0; y<kGridDown; y++ )
+			{
+				update[player][x][y] = false;
+			}
+		}
+	}
+}
+
+void UpdatePlayerWindow( int player )
+{
+	SDL_Rect fullSDLRect, offsetSDLRect;
+	int      x, y;
+	
+	if( control[player] == kNobodyControl ) return;
+	
+	if( playerWindowVisible[player] && refresh[player] )
+	{
+		MRect updateRect = {0, 0, 0, 0}, fullRect, offsetRect;
+		MBoolean first = true;
+		
+		for( x=0; x<kGridAcross; x++ )
+		{
+			for( y=1; y<kGridDown; y++ )
+			{
+				if( update[player][x][y] )
+				{
+					updateRect.top  = y * kBlobVertSize;
+					updateRect.left = x * kBlobHorizSize;
+					updateRect.bottom = updateRect.top + kBlobVertSize;
+					updateRect.right = updateRect.left + kBlobHorizSize;
+					if( first )
+					{
+						fullRect = updateRect;
+						first = false;
+					}
+					else
+					{
+						UnionMRect( &fullRect, &updateRect, &fullRect );
+					}
+
+					update[player][x][y] = false;
+				}
+			}
+		}
+
+		if( !first )
+		{
+			offsetRect = fullRect;
+			OffsetMRect( &offsetRect, playerWindowRect[player].left, playerWindowRect[player].top - kBlobVertSize );
+			
+			SDLU_BlitFrontSurface( playerSpriteSurface[player], 
+								   SDLU_MRectToSDLRect( &fullRect, &fullSDLRect ),
+								   SDLU_MRectToSDLRect( &offsetRect, &offsetSDLRect ) );
+		}
+	}
+}
+
+void SetUpdateRect( int player, MRect *where )
+{
+	int x,y;
+	int xMin, xMax, yMin, yMax;
+	
+	xMin = where->left / kBlobHorizSize;
+	xMax = ( where->right + kBlobHorizSize - 1 ) / kBlobHorizSize;
+	
+	if( xMin < 0 ) xMin = 0;
+	if( xMin > (kGridAcross-1) ) xMin = kGridAcross-1;
+	if( xMax < 0 ) xMax = 0;
+	if( xMax > kGridAcross ) xMax = kGridAcross;
+	
+	yMin = where->top / kBlobVertSize;
+	yMax = ( where->bottom + kBlobVertSize - 1 ) / kBlobVertSize;
+
+	if( yMin < 0 ) yMin = 0;
+	if( yMin > (kGridDown-1) ) yMin = kGridDown-1;
+	if( yMax < 0 ) yMax = 0;
+	if( yMax > kGridDown ) yMax = kGridDown;
+	
+	for( x=xMin; x<xMax; x++ )
+	{
+		for( y=yMin; y<yMax; y++ )
+		{
+			update[player][x][y] = true;
+		}
+	}
+	
+	refresh[player] = true;
+}
+
+
+void SurfaceBlitMask( SDL_Surface* object,     SDL_Surface* mask,     SDL_Surface* dest,
+                      const MRect*  objectRect, const MRect*  maskRect, const MRect*  destRect )
+{
+	int            startX = 0, startY = 0, endX, endY, x, y, srcRowBytes, mskRowBytes, dstRowBytes;
+	unsigned long  bit, startBit, maskBits;
+	unsigned char *src, *msk, *dst;
+	MRect          destBounds;
+
+	SDLU_SDLRectToMRect( &dest->clip_rect, &destBounds );
+	
+	endX = objectRect->right - objectRect->left;
+	endY = objectRect->bottom - objectRect->top;
+
+	if( destRect->left   > destBounds.right  ||			// completely clipped?
+		destRect->right  < destBounds.left   ||
+		destRect->top    > destBounds.bottom ||
+		destRect->bottom < destBounds.top )
+	{
+		return; 												// do nothing
+	}
+	
+	src         = (unsigned char*) object->pixels;
+	msk         = (unsigned char*) mask->pixels;
+	dst         = (unsigned char*) dest->pixels;
+	srcRowBytes = object->pitch;
+	mskRowBytes = mask->pitch;
+	dstRowBytes = dest->pitch;
+	
+	src += (objectRect->top * srcRowBytes) + (objectRect->left * 2);
+	msk += (maskRect->top   * mskRowBytes) + (maskRect->left   / 8);
+	dst += (destRect->top   * dstRowBytes) + (destRect->left   * 2);
+	
+	if( destRect->left   < destBounds.left   ) startX -= destRect->left - destBounds.left;
+	if( destRect->right  > destBounds.right  ) endX   -= destRect->right - destBounds.right;
+	if( destRect->top    < destBounds.top    ) startY -= destRect->top - destBounds.top;
+	if( destRect->bottom > destBounds.bottom ) endY   -= destRect->bottom - destBounds.bottom;
+	
+	startBit = 0x80000000 >> (startX & 31);
+	msk += (mskRowBytes * startY) + ((startX & ~31) / 8);
+	src += (srcRowBytes * startY) + (startX * 2);
+	dst += (dstRowBytes * startY) + (startX * 2);
+
+	for( y=startY; y<endY; y++ )
+	{
+		unsigned char *tSrc = src, *tDst = dst, *tMsk = msk;
+		
+		maskBits = SDL_SwapBE32( *(unsigned long*)msk );
+		bit = startBit;
+		
+		for( x=startX; x<endX; x++ )
+		{
+			if( maskBits & bit )
+			{
+				*(short*)dst = *(short*)src;
+			}
+			
+			if( !(bit >>= 1) ) { msk += 4; maskBits = SDL_SwapBE32( *(unsigned long*)msk ); bit = 0x80000000; }
+			src += 2; dst += 2;
+		}
+		
+		src = tSrc + srcRowBytes;
+		dst = tDst + dstRowBytes;
+		msk = tMsk + mskRowBytes;
+	}
+}
+
+
+void SurfaceBlitBlob( const MRect* blobRect, const MRect* destRect )
+{
+	SurfaceBlitMask( blobSurface, maskSurface, SDLU_GetCurrentSurface(),
+	                 blobRect,    blobRect,    destRect                  );
+}
+
+
+void SurfaceBlitColor( SDL_Surface* mask,     SDL_Surface* dest,
+                       const MRect* maskRect, const MRect* destRect, 
+                       int r, int g, int b, int weight )
+{
+	int            startX = 0, startY = 0, endX, endY, x, y, mskRowBytes, dstRowBytes;
+	unsigned long  bit, startBit, maskBits;
+	unsigned char *msk, *dst;
+	MRect          destBounds;
+	
+	endX = maskRect->right - maskRect->left;
+	endY = maskRect->bottom - maskRect->top;
+	
+	SDLU_SDLRectToMRect( &dest->clip_rect, &destBounds );
+
+	if( destRect->left   > destBounds.right  ||			// completely clipped?
+		destRect->right  < destBounds.left   ||
+		destRect->top    > destBounds.bottom ||
+		destRect->bottom < destBounds.top )
+	{
+		return; 												// do nothing
+	}
+	
+	msk         = (unsigned char*) mask->pixels;
+	dst         = (unsigned char*) dest->pixels;
+	mskRowBytes = mask->pitch;
+	dstRowBytes = dest->pitch;
+	
+	msk += (maskRect->top * mskRowBytes) + (maskRect->left / 8);
+	dst += (destRect->top * dstRowBytes) + (destRect->left * 2);
+	
+	if( destRect->left   < destBounds.left   ) startX -= destRect->left - destBounds.left;
+	if( destRect->right  > destBounds.right  ) endX   -= destRect->right - destBounds.right;
+	if( destRect->top    < destBounds.top    ) startY -= destRect->top - destBounds.top;
+	if( destRect->bottom > destBounds.bottom ) endY   -= destRect->bottom - destBounds.bottom;
+	
+	startBit = 0x80000000 >> (startX & 31);
+	msk += (mskRowBytes * startY) + ((startX & ~31) / 8);
+	dst += (dstRowBytes * startY) + (startX * 2);
+
+	r *= weight;
+	g *= weight;
+	b *= weight;
+	weight = 32 - weight;
+	
+	for( y=startY; y<endY; y++ )
+	{
+		unsigned char *tMsk = msk, *tDst = dst;
+		int work, workB, workG, workR;
+		
+		maskBits = SDL_SwapBE32( *(unsigned long*)msk );
+		bit = startBit;
+				
+		for( x=startX; x<endX; x++ )
+		{
+			if( maskBits & bit )
+			{
+				work = *(short*)dst;
+				workB = ((((work    ) & 0x001F)*weight) + b) >> 5;
+				workG = ((((work>> 5) & 0x001F)*weight) + g);
+				workR = ((((work>>10) & 0x001F)*weight) + r) << 5;
+
+				*(short*)dst = (workR & 0x7C00) | (workG & 0x03E0) | (workB & 0x001F);
+			}
+			
+			if( !(bit >>= 1) ) { msk += 4; maskBits = SDL_SwapBE32( *(unsigned long*)msk ); bit = 0x80000000; }
+			dst += 2;
+		}
+		
+		msk = tMsk + mskRowBytes;
+		dst = tDst + dstRowBytes;
+	}
+}
+
+
+void SurfaceBlitAlpha( SDL_Surface* back,     SDL_Surface* source,     SDL_Surface* alpha,     SDL_Surface* dest,
+                       const MRect* backRect, const MRect* sourceRect, const MRect* alphaRect, const MRect* destRect )
+{
+	int startX = 0, startY = 0, endX, endY, x, y, srcRowBytes, alfRowBytes, dstRowBytes, bckRowBytes;
+	unsigned char *bck, *src, *alf, *dst;
+	MRect destBounds;
+	
+	endX = sourceRect->right - sourceRect->left;
+	endY = sourceRect->bottom - sourceRect->top;
+	
+	SDLU_SDLRectToMRect( &dest->clip_rect, &destBounds );
+
+	if( destRect->left   > destBounds.right  ||			// completely clipped?
+		destRect->right  < destBounds.left   ||
+		destRect->top    > destBounds.bottom ||
+		destRect->bottom < destBounds.top )
+	{
+		return; 												// do nothing
+	}
+	
+	bck         = (unsigned char*) back->pixels;
+	src         = (unsigned char*) source->pixels; 
+	alf         = (unsigned char*) alpha->pixels;
+	dst         = (unsigned char*) dest->pixels; 
+	bckRowBytes = back->pitch; 
+	srcRowBytes = source->pitch;
+	alfRowBytes = alpha->pitch;
+	dstRowBytes = dest->pitch;
+	
+	bck += (backRect->top   * bckRowBytes) + (backRect->left   * 2);
+	src += (sourceRect->top * srcRowBytes) + (sourceRect->left * 2);
+	alf += (alphaRect->top  * alfRowBytes) + (alphaRect->left  * 2);
+	dst += (destRect->top   * dstRowBytes) + (destRect->left   * 2);
+	
+	if( destRect->left   < destBounds.left   ) startX -= destRect->left   - destBounds.left;
+	if( destRect->right  > destBounds.right  ) endX   -= destRect->right  - destBounds.right;
+	if( destRect->top    < destBounds.top    ) startY -= destRect->top    - destBounds.top;
+	if( destRect->bottom > destBounds.bottom ) endY   -= destRect->bottom - destBounds.bottom;
+	
+	bck += (bckRowBytes * startY) + (startX * 2);
+	src += (srcRowBytes * startY) + (startX * 2);
+	alf += (alfRowBytes * startY) + (startX * 2);
+	dst += (dstRowBytes * startY) + (startX * 2);
+
+	for( y=startY; y<endY; y++ )
+	{
+		unsigned char *tSrc = src, *tAlf = alf, *tDst = dst, *tBck = bck;
+		int pixS, pixB, weightS, weightB, workB, workG, workR;
+		
+		for( x=startX; x<endX; x++ )
+		{
+			weightB = *(short*)alf;
+			
+			if( weightB == 0x7FFF )
+			{
+				*(short*)dst = *(short*)bck;
+			}
+			else if( weightB == 0 )
+			{
+				*(short*)dst = *(short*)src;
+			}
+			else
+			{
+				weightS = ~weightB;
+
+				pixS = *(short*)src;
+				pixB = *(short*)bck;
+								
+				workB = ((((pixS      ) & 0x001F) * ((weightS      ) & 0x001F)) + (((pixB      ) & 0x001F) * ((weightB      ) & 0x001F))) >> 5;
+				workG = ((((pixS >>  5) & 0x001F) * ((weightS >>  5) & 0x001F)) + (((pixB >>  5) & 0x001F) * ((weightB >>  5) & 0x001F)));
+				workR = ((((pixS >> 10) & 0x001F) * ((weightS >> 10) & 0x001F)) + (((pixB >> 10) & 0x001F) * ((weightB >> 10) & 0x001F))) << 5;
+
+				*(short*)dst = (workR & 0x7C00) | (workG & 0x03E0) | (workB & 0x001F);
+			}
+
+			src += 2;
+			alf += 2;
+			bck += 2;
+			dst += 2;
+		}
+		
+		bck = tBck + bckRowBytes;
+		src = tSrc + srcRowBytes;
+		alf = tAlf + alfRowBytes;
+		dst = tDst + dstRowBytes;
+	}
+}
+
+
+void SurfaceBlitWeightedDualAlpha( SDL_Surface* back,     SDL_Surface* source,     SDL_Surface* mask,     SDL_Surface* alpha,     SDL_Surface* dest,
+                                   const MRect* backRect, const MRect* sourceRect, const MRect* maskRect, const MRect* alphaRect, const MRect* destRect,
+                                   int inWeight )
+{
+	int startX = 0, startY = 0, endX, endY, x, y, 
+	    srcRowBytes, alfRowBytes, mskRowBytes, dstRowBytes, bckRowBytes;
+	unsigned long  bit, startBit, maskBits;
+	unsigned char *bck, *src, *alf, *msk, *dst;
+	MRect destBounds;
+
+	endX = sourceRect->right - sourceRect->left;
+	endY = sourceRect->bottom - sourceRect->top;
+	
+	SDLU_SDLRectToMRect( &dest->clip_rect, &destBounds );
+	
+	if( destRect->left   > destBounds.right  ||			// completely clipped?
+		destRect->right  < destBounds.left   ||
+		destRect->top    > destBounds.bottom ||
+		destRect->bottom < destBounds.top )
+	{
+		return; 												// do nothing
+	}
+	
+	bck = (unsigned char*) back->pixels;
+	src = (unsigned char*) source->pixels;
+	msk = (unsigned char*) mask->pixels;
+	alf = (unsigned char*) alpha->pixels;
+	dst = (unsigned char*) dest->pixels;
+	
+	bckRowBytes = back->pitch;
+	srcRowBytes = source->pitch;
+	mskRowBytes = mask->pitch;
+	alfRowBytes = alpha->pitch;
+	dstRowBytes = dest->pitch;
+	
+	bck += (backRect->top   * bckRowBytes) + (backRect->left   * 2);
+	src += (sourceRect->top * srcRowBytes) + (sourceRect->left * 2);
+	alf += (alphaRect->top  * alfRowBytes) + (alphaRect->left  * 2);
+	dst += (destRect->top   * dstRowBytes) + (destRect->left   * 2);
+	msk += (maskRect->top   * mskRowBytes) + (maskRect->left   / 8);
+	
+	if( destRect->left   < destBounds.left   ) startX -= destRect->left - destBounds.left;
+	if( destRect->right  > destBounds.right  ) endX   -= destRect->right - destBounds.right;
+	if( destRect->top    < destBounds.top    ) startY -= destRect->top - destBounds.top;
+	if( destRect->bottom > destBounds.bottom ) endY   -= destRect->bottom - destBounds.bottom;
+	
+	bck += (bckRowBytes * startY) + (startX * 2);
+	src += (srcRowBytes * startY) + (startX * 2);
+	alf += (alfRowBytes * startY) + (startX * 2);
+	dst += (dstRowBytes * startY) + (startX * 2);
+	msk += (mskRowBytes * startY) + ((startX & ~31) / 8);
+	startBit = 0x80000000 >> (startX & 31);
+	
+	for( y=startY; y<endY; y++ )
+	{
+		unsigned char *tSrc = src, *tAlf = alf, *tDst = dst, *tBck = bck, *tMsk = msk;
+		int pixS, pixB, weightS, weightB, work, workB, workG, workR;
+		
+		maskBits = SDL_SwapBE32( *(unsigned long*)msk );
+		bit = startBit;
+		
+		for( x=startX; x<endX; x++ )
+		{
+			if( maskBits & bit )
+			{
+				work = *(short*)alf;
+				workB = ((((work    ) & 0x001F)*inWeight) ) >> 5;
+				workG = ((((work>> 5) & 0x001F)*inWeight) );
+				workR = ((((work>>10) & 0x001F)*inWeight) ) << 5;
+
+				weightB = ~((workR & 0x7C00) | (workG & 0x03E0) | (workB & 0x001F));
+				
+				if( weightB == 0x7FFF )
+				{
+					*(short*)dst = *(short*)bck;
+				}
+				else if( weightB == 0 )
+				{
+					*(short*)dst = *(short*)src;
+				}
+				else
+				{
+					weightS = ~weightB;
+
+					pixS = *(short*)src;
+					pixB = *(short*)bck;
+									
+					workB = ((((pixS      ) & 0x001F) * ((weightS      ) & 0x001F)) + (((pixB      ) & 0x001F) * ((weightB      ) & 0x001F))) >> 5;
+					workG = ((((pixS >>  5) & 0x001F) * ((weightS >>  5) & 0x001F)) + (((pixB >>  5) & 0x001F) * ((weightB >>  5) & 0x001F)));
+					workR = ((((pixS >> 10) & 0x001F) * ((weightS >> 10) & 0x001F)) + (((pixB >> 10) & 0x001F) * ((weightB >> 10) & 0x001F))) << 5;
+
+					*(short*)dst = (workR & 0x7C00) | (workG & 0x03E0) | (workB & 0x001F);
+				}
+			}
+
+			if( !(bit >>= 1) ) { msk += 4; maskBits = SDL_SwapBE32( *(unsigned long*)msk ); bit = 0x80000000; }
+			src += 2;
+			alf += 2;
+			bck += 2;
+			dst += 2;
+		}
+		
+		bck = tBck + bckRowBytes;
+		src = tSrc + srcRowBytes;
+		alf = tAlf + alfRowBytes;
+		dst = tDst + dstRowBytes;
+		msk = tMsk + mskRowBytes;
+	}
+}
+
+void SurfaceBlitWeightedCharacter( SkittlesFontPtr font, unsigned char text, MPoint *dPoint, int r, int g, int b, int alpha )
+{		
+	if( alpha == 31 )
+	{
+		SurfaceBlitCharacter( font, text, dPoint, r, g, b, 0 );
+	}
+	else if( alpha == 0 )
+	{
+		return;
+	}
+	else
+	{
+		SDL_Surface*   destSurface;
+		unsigned char* src;
+		unsigned char* dst;
+		int            srcRowBytes;
+		int            dstRowBytes;
+		int            index;
+		MRect          destBounds;
+		
+		int height = font->surface->h;
+		int width  = font->width[text];
+		int across = font->across[text];
+		
+		destSurface = SDLU_GetCurrentSurface();
+		SDLU_SDLRectToMRect( &destSurface->clip_rect, &destBounds );
+		
+		if( (dPoint->h + width)  > destBounds.right           ||      // clipped?
+		    (dPoint->v + height) > destBounds.bottom          || 
+		     dPoint->h           < destBounds.left            ||
+		     dPoint->v           < destBounds.top                )
+		{
+			dPoint->h += width;
+			return;                                               // do nothing
+		}
+		
+		srcRowBytes = font->surface->pitch;
+		dstRowBytes = destSurface->pitch;
+		
+		src = (unsigned char*) font->surface->pixels + across;
+		dst = (unsigned char*) destSurface->pixels + (dPoint->h * 2) + (dPoint->v * dstRowBytes);
+		
+		while( height-- )
+		{
+			unsigned char *tSrc = src, *tDst = dst;
+			
+			for( index=0; index<width; index++ )
+			{
+				int workR, workG, workB, work, weightS, weightD;
+				weightS = (*src >> 3) & 0x1F;
+				
+				weightS = (weightS * alpha) >> 5;
+				
+				if( weightS )
+				{
+					weightD = 32 - weightS;
+
+					work = *(short*)dst;
+					workB = ((((work    ) & 0x001F) * weightD) + (b * weightS)) >> 5;
+					workG = ((((work>> 5) & 0x001F) * weightD) + (g * weightS));
+					workR = ((((work>>10) & 0x001F) * weightD) + (r * weightS)) << 5;
+
+					*(short*)dst = (workR & 0x7C00) | (workG & 0x03E0) | (workB & 0x001F);
+				}
+				
+				src++;
+				dst+=2;
+			}
+			
+			src = tSrc + srcRowBytes;
+			dst = tDst + dstRowBytes;
+		}
+			
+		dPoint->h += width;
+	}
+}
+
+void SurfaceBlitCharacter( SkittlesFontPtr font, unsigned char text, MPoint *dPoint, int r, int g, int b, int dropShadow )
+{
+	SDL_Surface*    destSurface;
+	unsigned char*  src;
+	unsigned char*  dst;
+	int             srcRowBytes;
+	int             dstRowBytes;
+	int             index;
+	int             rgb555;
+	MRect           destBounds;
+	
+	int height = font->surface->h;
+	int width  = font->width[text];
+	int across = font->across[text];
+	
+	destSurface = SDLU_GetCurrentSurface();
+	SDLU_SDLRectToMRect( &destSurface->clip_rect, &destBounds );	
+	
+	if( (dPoint->h + width)  > destBounds.right           ||      // clipped?
+	    (dPoint->v + height) > destBounds.bottom          || 
+	     dPoint->h           < destBounds.left            ||
+	     dPoint->v           < destBounds.top                )
+	{
+		dPoint->h += width;
+		return;                                               // do nothing
+	}
+	
+	srcRowBytes = font->surface->pitch;
+	dstRowBytes = destSurface->pitch;
+	
+	src = (unsigned char*) font->surface->pixels + across;
+	dst = (unsigned char*) destSurface->pixels + (dPoint->h * 2) + (dPoint->v * dstRowBytes);
+	rgb555 = (r << 10) | (g << 5) | b;
+	
+	switch( dropShadow )
+	{
+		case 0:
+			while( height-- )
+			{
+				unsigned char *tSrc = src, *tDst = dst;
+				
+				for( index=0; index<width; index++ )
+				{
+					int workR, workG, workB, work, weightS, weightD;
+					weightS = (*src >> 3) & 0x1F;
+					
+					if( weightS == 31 )
+					{
+						*(short*)dst = rgb555;
+					}
+					else if( weightS > 0 )
+					{
+						weightD = 32 - weightS;
+
+						work = *(short*)dst;
+						workB = ((((work    ) & 0x001F) * weightD) + (b * weightS)) >> 5;
+						workG = ((((work>> 5) & 0x001F) * weightD) + (g * weightS));
+						workR = ((((work>>10) & 0x001F) * weightD) + (r * weightS)) << 5;
+
+						*(short*)dst = (workR & 0x7C00) | (workG & 0x03E0) | (workB & 0x001F);
+					}
+					
+					src++;
+					dst+=2;
+				}
+				
+				src = tSrc + srcRowBytes;
+				dst = tDst + dstRowBytes;
+			}
+			break;
+		
+		default:
+			{
+				unsigned char *drp = dst + ((dstRowBytes + 2) * dropShadow);
+				
+				while( height-- )
+				{
+					unsigned char *tSrc = src, *tDst = dst, *tDrp = drp;
+					
+					for( index=0; index<width; index++ )
+					{
+						int workR, workG, workB, work, weightS, weightD;
+						weightS = (*src >> 3) & 0x1F;
+						
+						if( weightS == 31 )
+						{
+							*(short*)drp = 0;
+							*(short*)dst = rgb555;
+						}
+						else if( weightS > 0 )
+						{
+							weightD = 32 - weightS;
+
+							work = *(short*)drp;
+							workB = ((((work    ) & 0x001F) * weightD)) >> 5;
+							workG = ((((work>> 5) & 0x001F) * weightD));
+							workR = ((((work>>10) & 0x001F) * weightD)) << 5;
+							*(short*)drp = (workR & 0x7C00) | (workG & 0x03E0) | (workB & 0x001F);
+
+							work = *(short*)dst;
+							workB = ((((work    ) & 0x001F) * weightD) + (b * weightS)) >> 5;
+							workG = ((((work>> 5) & 0x001F) * weightD) + (g * weightS));
+							workR = ((((work>>10) & 0x001F) * weightD) + (r * weightS)) << 5;
+							*(short*)dst = (workR & 0x7C00) | (workG & 0x03E0) | (workB & 0x001F);
+						}
+						
+						src++;
+						dst+=2;
+						drp+=2;
+					}
+					
+					src = tSrc + srcRowBytes;
+					dst = tDst + dstRowBytes;
+					drp = tDrp + dstRowBytes;
+				}
+				break;
+			}
+	}	
+	dPoint->h += width;
+}
+
+void SurfaceBlitColorOver( SDL_Surface* source,     SDL_Surface* dest,
+                           const MRect* sourceRect, const MRect* destRect,
+                           int r, int g, int b, int weight )
+{
+	int            startX = 0, startY = 0, endX, endY, x, y, dstRowBytes, srcRowBytes;
+	unsigned char* src;
+	unsigned char* dst;
+	MRect          destBounds;
+	
+	SDLU_SDLRectToMRect( &dest->clip_rect, &destBounds );
+	
+	endX = destRect->right - destRect->left;
+	endY = destRect->bottom - destRect->top;
+	
+	if( destRect->left   > destBounds.right  ||			// completely clipped?
+		destRect->right  < destBounds.left   ||
+		destRect->top    > destBounds.bottom ||
+		destRect->bottom < destBounds.top )
+	{
+		return; 												// do nothing
+	}
+	
+	src         = (unsigned char*) source->pixels;
+	dst         = (unsigned char*) dest->pixels;
+	srcRowBytes = source->pitch;
+	dstRowBytes = dest->pitch;
+	
+	src += (sourceRect->top * srcRowBytes) + (sourceRect->left * 2);
+	dst += (destRect->top * dstRowBytes) + (destRect->left * 2);
+	
+	if( destRect->left   < destBounds.left   ) startX -= destRect->left - destBounds.left;
+	if( destRect->right  > destBounds.right  ) endX   -= destRect->right - destBounds.right;
+	if( destRect->top    < destBounds.top    ) startY -= destRect->top - destBounds.top;
+	if( destRect->bottom > destBounds.bottom ) endY   -= destRect->bottom - destBounds.bottom;
+	
+	src += (srcRowBytes * startY) + (startX * 2);
+	dst += (dstRowBytes * startY) + (startX * 2);
+
+	r *= weight;
+	g *= weight;
+	b *= weight;
+	weight = 32 - weight;
+	
+	for( y=startY; y<endY; y++ )
+	{
+		unsigned char *tSrc = src, *tDst = dst;
+		int work, workB, workG, workR;
+		
+		for( x=startX; x<endX; x++ )
+		{
+			work = *(short*)src;
+			workB = ((((work    ) & 0x001F)*weight) + b) >> 5;
+			workG = ((((work>> 5) & 0x001F)*weight) + g);
+			workR = ((((work>>10) & 0x001F)*weight) + r) << 5;
+
+			*(short*)dst = (workR & 0x7C00) | (workG & 0x03E0) | (workB & 0x001F);
+			
+			src += 2;
+			dst += 2;
+		}
+		
+		src = tSrc + srcRowBytes;
+		dst = tDst + dstRowBytes;
+	}
+}
+
+// NOTE: due to rounding error, there should never be a case of transitioning all the way from 0 to 31.
+// You'll overflow and get weird glitches on the edges.
+
+void SurfaceBlitBlendOver( SDL_Surface* source,     SDL_Surface* dest,
+                           const MRect* sourceRect, const MRect* destRect, 
+                           int r1, int g1, int b1, 
+                           int r2, int g2, int b2, 
+                           int r3, int g3, int b3, 
+                           int r4, int g4, int b4, 
+                           int weight )
+{
+	int startX = 0, startY = 0, endX, endY, x, y;
+	int rA, gA, bA;
+	int rB, gB, bB;
+	int rI, gI, bI;
+	int vrLI, vgLI, vbLI;
+	int vrRI, vgRI, vbRI;
+	int weight16, ditherDiff;
+	int width, height, dstRowBytes, srcRowBytes;
+	unsigned char *src, *dst;
+	MRect destBounds;
+	MBoolean oddX = false;
+
+	SDLU_SDLRectToMRect( &dest->clip_rect, &destBounds );
+	
+	if( destRect->left   > destBounds.right  ||			// completely clipped?
+		destRect->right  < destBounds.left   ||
+		destRect->top    > destBounds.bottom ||
+		destRect->bottom < destBounds.top )
+	{
+		return; 												// do nothing
+	}
+
+	endX = destRect->right - destRect->left;
+	endY = destRect->bottom - destRect->top;
+	
+	src         = (unsigned char*) source->pixels; 
+	dst         = (unsigned char*) dest->pixels;
+	srcRowBytes = source->pitch;
+	dstRowBytes = dest->pitch;
+	
+	src += (sourceRect->top * srcRowBytes) + (sourceRect->left * 2);
+	dst += (destRect->top * dstRowBytes)   + (destRect->left * 2);
+	
+	if( destRect->left   < destBounds.left   ) startX -= destRect->left - destBounds.left;
+	if( destRect->right  > destBounds.right  ) endX   -= destRect->right - destBounds.right;
+	if( destRect->top    < destBounds.top    ) startY -= destRect->top - destBounds.top;
+	if( destRect->bottom > destBounds.bottom ) endY   -= destRect->bottom - destBounds.bottom;
+	
+	src += (srcRowBytes * startY) + (startX * 2);
+	dst += (dstRowBytes * startY) + (startX * 2);
+
+	height = endY - startY;
+	width  = endX - startX;
+	
+	weight16 = weight << 16;
+	
+	r1 *= weight16;		g1 *= weight16;		b1 *= weight16;	
+	r2 *= weight16;		g2 *= weight16;		b2 *= weight16;	
+	r3 *= weight16;		g3 *= weight16;		b3 *= weight16;	
+	r4 *= weight16;		g4 *= weight16;		b4 *= weight16;	
+	ditherDiff = weight16 >> 1;
+	
+	vrLI = (r3 - r1) / height;
+	vgLI = (g3 - g1) / height;
+	vbLI = (b3 - b1) / height;
+
+	vrRI = (r4 - r2) / height;
+	vgRI = (g4 - g2) / height;
+	vbRI = (b4 - b2) / height;
+
+	weight = 32 - weight;
+	
+	if( (endX - startX) & 1 )
+	{
+		endX--;
+		oddX = true;
+	}
+	
+	for( y=startY; y<endY; y++ )
+	{
+		unsigned char *tSrc = src, *tDst = dst;
+		int work, workB, workG, workR;
+		
+		if( y & 1 )
+		{
+			rA =  r1;
+			gA =  g1;
+			bA =  b1;
+			rB =  r1 - ditherDiff; if( rB < 0 ) rB = 0;
+			gB =  g1 - ditherDiff; if( gB < 0 ) gB = 0;
+			bB =  b1 - ditherDiff; if( bB < 0 ) bB = 0;
+		}
+		else
+		{
+			rA =  r1 - ditherDiff; if( rA < 0 ) rA = 0;
+			gA =  g1 - ditherDiff; if( gA < 0 ) gA = 0;
+			bA =  b1 - ditherDiff; if( bA < 0 ) bA = 0;
+			rB =  r1;
+			gB =  g1;
+			bB =  b1;
+		}
+		
+		rI = (2 * (r2 - r1)) / width;
+		gI = (2 * (g2 - g1)) / width;
+		bI = (2 * (b2 - b1)) / width;
+					
+		for( x=startX; x<endX; x+=2 )
+		{
+			work = *(short*)src;
+			workB = ((((work<<16) & 0x001F0000)*weight) + bA) >> 21;
+			workG = ((((work<<11) & 0x001F0000)*weight) + gA) >> 16;
+			workR = ((((work<< 6) & 0x001F0000)*weight) + rA) >> 11;
+
+			*(short*)dst = (workR & 0x7C00) | (workG & 0x03E0) | (workB & 0x001F);
+			
+			src+= 2;
+			dst+= 2;			
+			rA += rI; 
+			gA += gI;
+			bA += bI;
+
+			work = *(short*)src;
+			workB = ((((work<<16) & 0x001F0000)*weight) + bB) >> 21;
+			workG = ((((work<<11) & 0x001F0000)*weight) + gB) >> 16;
+			workR = ((((work<< 6) & 0x001F0000)*weight) + rB) >> 11;
+
+			*(short*)dst = (workR & 0x7C00) | (workG & 0x03E0) | (workB & 0x001F);
+			
+			src+= 2;
+			dst+= 2;			
+			rB += rI;
+			gB += gI;
+			bB += bI;
+		}
+		
+		if( oddX )
+		{
+			work = *(short*)src;
+			workB = ((((work<<16) & 0x001F0000)*weight) + bA) >> 21;
+			workG = ((((work<<11) & 0x001F0000)*weight) + gA) >> 16;
+			workR = ((((work<< 6) & 0x001F0000)*weight) + rA) >> 11;
+
+			*(short*)dst = (workR & 0x7C00) | (workG & 0x03E0) | (workB & 0x001F);
+		}
+		
+		src = tSrc + srcRowBytes;
+		dst = tDst + dstRowBytes;
+
+		r1 += vrLI; r2 += vrRI;
+		g1 += vgLI; g2 += vgRI;
+		b1 += vbLI; b2 += vbRI;
+	}
+}
+
--- /dev/null
+++ b/Source/blitter.h
@@ -1,0 +1,48 @@
+// blitter.h
+
+#include "SDL.h"
+
+#include "font.h"
+
+// SDL type
+void SurfaceBlitMask( SDL_Surface* object,     SDL_Surface* mask,     SDL_Surface* dest,
+                      const MRect* objectRect, const MRect* maskRect, const MRect* destRect );
+
+void SurfaceBlitBlob( const MRect* blobRect, const MRect* destRect ); 
+
+void SurfaceBlitColor( SDL_Surface* mask,     SDL_Surface* dest,
+                       const MRect* maskRect, const MRect* destRect, 
+                       int r, int g, int b, int weight );
+
+void SurfaceBlitAlpha( SDL_Surface* back,     SDL_Surface* source,     SDL_Surface* alpha,     SDL_Surface* dest,
+                       const MRect* backRect, const MRect* sourceRect, const MRect* alphaRect, const MRect* destRect );
+
+void SurfaceBlitWeightedDualAlpha( SDL_Surface* back,     SDL_Surface* source,  SDL_Surface* mask,     SDL_Surface* alpha,     SDL_Surface* dest,
+                                   const MRect* backRect, const MRect* srcRect, const MRect* maskRect, const MRect* alphaRect, const MRect* destRect,
+                                   int inWeight );
+
+void SurfaceBlitWeightedCharacter( SkittlesFontPtr font, unsigned char text, MPoint *dPoint, int r, int g, int b, int alpha );
+
+void SurfaceBlitCharacter( SkittlesFontPtr font, unsigned char text, MPoint *dPoint, int r, int g, int b, int dropShadow );
+
+void SurfaceBlitBlendOver( SDL_Surface* source,     SDL_Surface* dest,
+                           const MRect* sourceRect, const MRect* destRect, 
+                           int r1, int g1, int b1, 
+                           int r2, int g2, int b2, 
+                           int r3, int g3, int b3, 
+                           int r4, int g4, int b4, 
+                           int weight );
+                          
+void SurfaceBlitColorOver( SDL_Surface* source,     SDL_Surface* dest,
+                           const MRect* sourceRect, const MRect* destRect,
+                           int r, int g, int b, int weight );
+
+
+
+void SetUpdateRect( int player, MRect *where );
+void UpdatePlayerWindow( int player );
+void InitBlitter( void );
+
+extern MBoolean update[2][kGridAcross][kGridDown];
+extern MBoolean refresh[2];
+extern MPoint topLeft[2];
--- /dev/null
+++ b/Source/control.cpp
@@ -1,0 +1,629 @@
+// control.c
+
+#include "main.h"
+#include "control.h"
+#include "moving.h"
+#include "players.h"
+#include "random.h"
+#include "grays.h"
+#include "zap.h"
+#include "gameticks.h"
+#include "level.h"
+#include "tutorial.h"
+#include <stdlib.h>
+
+int destinationX[2], destinationR[2];
+signed char tempGrid[kGridAcross][kGridDown];
+long timeAI[2], timeMove[2];
+MBoolean moveQuick[2];
+AutoPatternPtr autoPattern = NULL;
+
+void AutoControl( int player )
+{
+	if( autoPattern ) 
+	{				
+		switch( autoPattern->command )
+		{
+			case kMessage:
+				StartBalloon( autoPattern->message );
+				autoPattern++;
+				break;
+				
+			case kIdleTicks:
+				if( !tutorialTime )
+				{
+					tutorialTime = GameTickCount() + autoPattern->d1;
+				}
+				else
+				{
+					if( GameTickCount() >= tutorialTime )
+					{
+						tutorialTime = 0;
+						autoPattern++;
+					}
+				}
+				break;
+			
+			case kRetrieve:
+				if( role[player] == kWaitForRetrieval )
+				{
+					nextA[player] = abs( autoPattern->d1 );
+					nextB[player] = abs( autoPattern->d2 );
+					nextM[player] = (autoPattern->d1 < 0);
+					nextG[player] = (autoPattern->d1 == kBombBottom) && (autoPattern->d2 == kBombTop);
+					
+					if( !nextG[player] )
+					{
+						nextA[player] = pieceMap[ nextA[player] ];
+						nextB[player] = pieceMap[ nextB[player] ];
+					}
+										
+					role[player]  = kRetrieveBlobs;
+					autoPattern++;
+				}
+				break;
+			
+			case kPosition:
+				if( (role[player] != kFalling) || (blobX[player] == autoPattern->d1) )
+				{
+					autoPattern++;
+				}
+				else if( GameTickCount() >= timeMove[player] )
+				{
+					timeMove[player] = GameTickCount() + 12;
+					
+					if( blobX[player] > autoPattern->d1 )
+					{
+						if( CanGoLeft( player ) )
+							GoLeft( player );
+					}
+					else
+					{
+						if( CanGoRight( player ) )
+							GoRight( player );
+					}
+				}
+				break;
+			
+			case kSpin:
+				if( role[player] != kFalling )
+				{
+					autoPattern++;
+				}
+				else if( CanRotate( player ) )
+				{
+					DoRotate( player );
+					autoPattern++;
+				}
+				break;
+
+			case kBlockUntilLand:
+				if( role[player] != kFalling ) 
+				{
+					autoPattern++;
+				}			
+				break;
+			
+			case kBlockUntilDrop:
+				if( !dropping[player] ) DoDrop( player );
+
+				if( role[player] != kFalling ) 
+				{
+					autoPattern++;
+				}			
+				break;
+
+			case kPunish:
+				if( role[player] == kWaitForRetrieval )
+				{
+					lockGrays[player] = autoPattern->d1;
+					SetupGrays( player );
+					blobTime[player] = GameTickCount( );
+					role[player] = kDropGrays;
+
+					autoPattern++;
+				}
+				break;
+		
+			case kComplete:
+				EndTutorial( );
+				break;
+			
+			case kBlockUntilComplete:
+				if( role[player] == kWaitForRetrieval ) 
+				{
+					autoPattern++;
+				}			
+				break;
+		}
+	}
+}
+
+void PlayerControl( int player )
+{
+	int a = player, b = player;
+	MBoolean moved = false;
+	
+	if( players == 1 )
+	{
+		a = 0;
+		b = 1;
+	}
+
+	if( hitKey[a].left || hitKey[b].left )
+	{
+		if( GameTickCount() >= timeMove[player] )
+		{
+			timeMove[player] += 12;
+			if( CanGoLeft( player ) )
+				GoLeft( player );
+		}
+		
+		moved = true;
+	}
+	
+	if( hitKey[a].right || hitKey[b].right )
+	{	
+		if( GameTickCount() >= timeMove[player] )
+		{
+			timeMove[player] += 12;
+			if( CanGoRight( player ) )
+				GoRight( player );
+		}
+		
+		moved = true;
+	}
+	
+	if( !moved ) timeMove[player] = GameTickCount( );
+	
+	if( hitKey[a].rotate == 1 || hitKey[b].rotate == 1 )
+	{
+		if( CanRotate( player ) )
+			DoRotate( player );
+	}
+	
+	if( hitKey[a].drop == 1 || hitKey[b].drop == 1 )
+	{
+		DoDrop( player );
+	}
+	
+	if( hitKey[a].drop == 0 && hitKey[b].drop == 0 )
+	{
+		StopDrop( player );
+	}
+}
+
+void AIControl( int player )
+{
+	if( timeAI[player] > GameTickCount() )
+		return;
+	
+	timeAI[player] += moveQuick[player]? character[player].speedRush:
+									     character[player].speedNormal;
+	
+	switch( RandomBefore( 2 ) )
+	{
+		case 0:
+			if( destinationR[player] != blobR[player] )
+			{
+				if( CanRotate(player) )
+				{
+					DoRotate( player );
+				}
+			}
+			break;
+		
+		case 1:
+			if( destinationX[player] != blobX[player] ) 
+			{
+				if( destinationX[player] > blobX[player] )
+				{
+					if( CanGoRight( player ) )
+						GoRight( player );
+				}
+				else
+				{
+					if( CanGoLeft( player ) )
+						GoLeft( player );
+				}
+			}
+			break;		
+	}
+	
+	if( destinationX[player] == blobX[player] &&
+		destinationR[player] == blobR[player] &&
+		RandomBefore( 100 ) < character[player].intellect )
+	{
+		DoDrop( player );
+	}
+}
+
+void ChooseAIDestination( int player )
+{
+	int testX, testR, testX2, testR2, value, bestValue = -9999999;
+	int x, y;
+	int bestX[kGridAcross*4], bestR[kGridAcross*4], currentBest = -1;
+	int rowDifference, totalTries, temp;
+	MBoolean shouldTry[kGridAcross][4];
+	
+	timeAI[player] = GameTickCount( ) + 1;
+	moveQuick[player] = true;
+	
+	if( grenade[player] )
+	{
+		bestValue = 0;
+		currentBest = 2;
+		
+		for( testX = 0; testX < kGridAcross; testX++ )
+		{
+			rowDifference = GetRowHeight( player, testX );
+			
+			if( (rowDifference < kGridDown) &&
+					(grid[player][testX][rowDifference+1] >= kFirstBlob) &&
+					(grid[player][testX][rowDifference+1] <= kLastBlob)     )
+			{
+				value = 0;
+
+				for( x=0; x<kGridAcross; x++ )
+				{
+					for( y=0; y<kGridDown; y++ )
+					{
+						if( grid[player][x][y] == grid[player][testX][rowDifference+1] ) value++;
+					}
+				}
+			
+				if( value > bestValue )
+				{
+					bestValue = value;
+					currentBest = testX;
+				}
+			}
+		}
+		
+		destinationR[player] = upRotate;
+		destinationX[player] = currentBest;
+		return;
+	}
+	
+	if( (GameTickCount() - startTime) <= 3600 )
+	{
+		for( testX = 0; testX < kGridAcross; testX++ )
+		{
+			rowDifference =  GetRowHeight( player, testX ) - character[player].autoSetup[testX];
+			
+			if( rowDifference >= 2 )
+			{
+				destinationR[player] = downRotate;
+				destinationX[player] = testX;
+				return;
+			}
+			
+			if( rowDifference == 1 )
+			{
+				destinationX[player] = testX;
+				
+				if( testX > 0 )
+				{
+					if( GetRowHeight( player, testX-1 ) > character[player].autoSetup[testX-1] )
+					{
+						destinationR[player] = leftRotate;
+						return;
+					}
+				}
+				
+				if( testX < (kGridAcross-1) )
+				{
+					if( GetRowHeight( player, testX+1 ) > character[player].autoSetup[testX+1] )
+					{
+						destinationR[player] = rightRotate;
+						return;
+					}
+				}
+				
+				destinationR[player] = upRotate;
+				return;
+			}
+		}
+	}
+	
+	moveQuick[player] = (emotions[player] == kEmotionSad) || (emotions[player] == kEmotionPanic);
+	
+	totalTries = character[player].intellect;
+	for( testX = 0; testX < kGridAcross; testX++ )
+	{
+		for( testR = 0; testR < 4; testR++ )
+		{
+			shouldTry[testX][testR] = --totalTries >= 0;
+		}
+	}
+	
+	for( testX = 0; testX < kGridAcross; testX++ )
+	{
+		for( testR = 0; testR < 4; testR++ )
+		{
+			testX2 = RandomBefore( kGridAcross );
+			testR2 = RandomBefore( 4 );
+		
+			temp = shouldTry[testX][testR];
+			shouldTry[testX][testR] = shouldTry[testX2][testR2];
+			shouldTry[testX2][testR2] = temp;
+		}
+	}
+	
+	shouldTry[0][leftRotate]			  = false;
+	shouldTry[kGridAcross-1][rightRotate] = false;
+		
+	for( testX = 0; testX < kGridAcross; testX++ )
+	{
+		for( testR = 0; testR<=3; testR++ )
+		{
+			if( shouldTry[testX][testR] )
+			{
+				value = TestAIDestination( player, testX, testR );
+				
+				if( value > bestValue )
+				{
+					bestValue = value;
+					currentBest = -1;
+				}
+				
+				if( value == bestValue )
+				{
+					currentBest++;
+					bestX[currentBest] = testX;
+					bestR[currentBest] = testR;
+				}
+			}
+		}
+	}
+	
+	currentBest = RandomBefore( currentBest + 1 );
+	destinationX[player] = bestX[currentBest];
+	destinationR[player] = bestR[currentBest];
+}
+
+int TestAIDestination( int player, int testX, int testR )
+{
+	int x, y, height, chains, rensa = 50, result = 0;
+
+	for( x=0; x<kGridAcross; x++ )
+	{
+		for( y=0; y<kGridDown; y++ )
+		{
+			tempGrid[x][y] = grid[player][x][y];
+		}
+	}
+		
+	height = GetRowHeight(player, testX);
+	switch( testR ) 
+	{
+		case upRotate:
+			tempGrid[testX][height--] = colorA[player];
+			if( height >= 0 ) tempGrid[testX][height]   = colorB[player];
+			break;
+		
+		case downRotate:
+			tempGrid[testX][height--] = colorB[player];
+			if( height >= 0 ) tempGrid[testX][height]   = colorA[player];
+			break;
+		
+		case leftRotate:
+			tempGrid[testX][height]                         = colorA[player];
+			tempGrid[testX-1][GetRowHeight(player,testX-1)] = colorB[player];
+			break;
+		
+		case rightRotate:
+			tempGrid[testX][height]                         = colorA[player];
+			tempGrid[testX+1][GetRowHeight(player,testX+1)] = colorB[player];
+			break;		
+	}
+	
+	chains = TestTemporaryGrid( );
+	
+	result = ScoreTemporaryGrid( );
+		
+	if( (chains < 2) && (character[player].intellect > (24 * 2/3)) )
+	{
+		rensa = 0;
+	}
+	else
+	{
+		while( chains-- ) rensa *= 10;
+	}
+	
+	result += rensa;
+	
+	return result;
+}
+
+int ScoreTemporaryGrid( void )
+{
+	int x, y, change, result = 0;
+	int deductions[kGridAcross][kGridDown] = 
+		{ { 400, 350, 350, 200, 120, 60, 20, 5, 0, 0, 0, 0 },
+		  { 600, 500, 300, 150, 100, 50, 20, 0, 0, 0, 0, 0 },
+		  { 9999, 800, 200, 100,  50, 40, 20, 0, 0, 0, 0, 0 },
+		  { 9999, 800, 200, 100,  50, 40, 20, 0, 0, 0, 0, 0 },
+		  { 9999, 500, 300, 150, 100, 50, 20, 0, 0, 0, 0, 0 },
+		  { 400, 350, 350, 200, 120, 60, 20, 5, 0, 0, 0, 0 } };
+
+	for( x=0; x<kGridAcross; x++ )
+	{
+		for( y=0; y<kGridDown; y++ )
+		{
+			if( tempGrid[x][y] == kEmpty )
+			{
+				result += deductions[x][y];
+			}
+			else if( tempGrid[x][y] == kGray )
+			{
+				result -= deductions[x][y];
+			}
+			else
+			{
+				change = 0;
+
+				if( y < (kGridDown-1)   && (tempGrid[x][y] == tempGrid[x][y+1])   ) change += 50;
+				if( y > 0               && (tempGrid[x][y] == tempGrid[x][y-1])   ) change += 50;
+				if( x < (kGridAcross-1) && (tempGrid[x][y] == tempGrid[x+1][y])   ) change += 40;
+				if( x > 0               && (tempGrid[x][y] == tempGrid[x-1][y])   ) change += 40;
+				if( x > 0               &&  
+				    y > 0               && (tempGrid[x][y] == tempGrid[x-1][y-1]) ) change += 20;
+				if( x < (kGridAcross-1) &&  
+				    y > 0               && (tempGrid[x][y] == tempGrid[x+1][y-1]) ) change += 20;
+				if( x > 0               &&  
+				    y < (kGridDown-1)   && (tempGrid[x][y] == tempGrid[x-1][y+1]) ) change += 10;
+				if( x < (kGridAcross-1) &&  
+				    y < (kGridDown-1)   && (tempGrid[x][y] == tempGrid[x+1][y+1]) ) change += 10;
+				if( y < (kGridDown-2)   && (tempGrid[x][y] == tempGrid[x][y+2])   ) change += 10;
+				if( y > 1               && (tempGrid[x][y] == tempGrid[x][y-2])   ) change += 10;
+				
+				if( (x > 0               && tempGrid[x-1][y] == kEmpty) ||
+				    (x < (kGridAcross-1) && tempGrid[x+1][y] == kEmpty) ||
+				    (y < 4               || tempGrid[x][y-4] == kEmpty)    ) change *= 4;
+				    
+				result += change / 4;
+			}
+		}
+	}
+	
+	return result;
+}
+
+int TestTemporaryGrid( void )
+{
+	MBoolean busy;
+	int x, y, stackSize, chains = 0;
+	
+	do
+	{
+		busy = false;
+		
+		for( x=0; x<kGridAcross; x++ )
+		{
+			for( y=0; y<kGridDown; y++ )
+			{
+				if( tempGrid[x][y] >= kFirstBlob && tempGrid[x][y] <= kLastBlob )
+				{
+					if( SizeUp( tempGrid, x, y, tempGrid[x][y] ) >= kBlobClusterSize )
+					{
+						QuickRemove( tempGrid, x, y, tempGrid[x][y] );
+						busy = true;
+					}
+				}
+			}
+		}
+		
+		if( busy )
+		{
+			chains++;
+			
+			for( x=0; x<kGridAcross; x++ )
+			{
+				stackSize = kGridDown-1;
+				
+				for( y=kGridDown-1; y>=0; y-- )
+				{
+					if( tempGrid[x][y] != kEmpty )
+					{
+						tempGrid[x][stackSize] = tempGrid[x][y];
+						if( y < stackSize ) tempGrid[x][y] = kEmpty;
+						stackSize--;
+					}
+				}
+			}
+		}
+	}
+	while( busy );
+	
+	return chains;
+}
+
+void QuickRemove( signed char myGrid[kGridAcross][kGridDown], int x, int y, int color )
+{
+	if( (x<0) || (x>=kGridAcross) || (y<0) || (y>=kGridDown) )
+		return;
+	
+	if( myGrid[x][y] == kGray ) myGrid[x][y] = kEmpty;	
+	if( myGrid[x][y] == color )
+	{
+		myGrid[x][y] = kEmpty;
+		QuickRemove( myGrid, x-1, y,   color );
+		QuickRemove( myGrid, x+1, y,   color );
+		QuickRemove( myGrid, x,   y-1, color );
+		QuickRemove( myGrid, x,   y+1, color );
+	}
+}
+
+int BestColor( int player, int blobX, int blobY )
+{
+	int x, y, color, bestColor = kFirstBlob, bestResult = -9999999, rensa, chains, result;
+//	char C[] = {' ', '*', '@', '.', '=', '+', 'o', 'J'};	
+
+
+	for( color = kFirstBlob; color <= kLastBlob; color++ )
+	{		
+		for( y=0; y<kGridDown; y++ )
+		{
+			for( x=0; x<kGridAcross; x++ )
+			{
+				tempGrid[x][y] = grid[player][x][y];
+			}
+		}
+	
+		tempGrid[blobX][blobY] = color;
+		
+		chains = TestTemporaryGrid( );
+		result = ScoreTemporaryGrid( );
+
+		rensa = 1000;
+		while( chains-- ) rensa *= 10;
+		
+		result += rensa;
+		
+		if( result > bestResult )
+		{
+			bestColor = color;
+			bestResult = result;
+		}
+	}
+		
+	return bestColor;
+}
+
+// Returns the first empty row.
+int GetRowHeight( int player, int row )
+{
+	int height;
+	
+	for( height = (kGridDown-1); height > 0; height-- )
+	{
+		if( grid[player][row][height] == kEmpty ) break;
+	}
+	
+	return height;
+}
+
+int DetermineEmotion( int player )
+{
+	int us = 0, them = 0, aboveWater = 1;
+	int x,y;
+	
+	if( role[player] == kLosing )  return kEmotionPanic;
+	if( role[player] == kWinning ) return kEmotionHappy;
+	
+	for( x=0; x<kGridAcross; x++ )
+	{
+		for( y=0; y<kGridDown; y++ )
+		{
+			if( aboveWater && (y<3) && (grid[player][x][y] != kEmpty) ) aboveWater = 0;
+			if( grid[player][x][y]   != kEmpty ) us++;
+			if( grid[1-player][x][y] != kEmpty ) them++;
+		}
+	}
+	
+	if( us > 48 && !aboveWater ) return kEmotionPanic;
+	else if( abs(us-them) < 12 ) return kEmotionNeutral;
+	else if( us > them ) return kEmotionSad;
+	else return kEmotionHappy;
+}
--- /dev/null
+++ b/Source/control.h
@@ -1,0 +1,28 @@
+// control.h
+
+void PlayerControl( int player );
+void AIControl( int player );
+void ChooseAIDestination( int player );
+int TestAIDestination( int player, int testX, int testR );
+int GetRowHeight( int player, int row );
+int DetermineEmotion( int player );
+int TestTemporaryGrid( void );
+void QuickRemove( signed char myGrid[kGridAcross][kGridDown], int x, int y, int color );
+MBoolean CanReach( int player, int testX );
+int ScoreTemporaryGrid( void );
+int BestColor( int player, int BlobX, int BlobY );
+void LogText( char string[] );
+void LogGrid( signed char myGrid[kGridAcross][kGridDown] );
+void AutoControl( int player );
+
+extern int destinationX[2], destinationR[2];
+extern signed char tempGrid[kGridAcross][kGridDown];
+extern long timeAI[2];
+extern MBoolean moveQuick[2];
+enum
+{
+	kEmotionNeutral = 0,
+	kEmotionSad,
+	kEmotionHappy,
+	kEmotionPanic
+};
--- /dev/null
+++ b/Source/fmodmusic.cpp
@@ -1,0 +1,83 @@
+// fmodmusic.c
+
+#include <string.h>
+
+#include "main.h"
+#include "music.h"
+#include "gworld.h"
+#include "gameticks.h"
+#include "soundfx.h"
+#include "graphics.h"
+
+#include "fmod.h"
+#include "fmod_errors.h"
+
+const int kNoMusic = -1;
+
+MBoolean musicOn = true, musicFast = false;
+int musicLevel = 0, musicSelection = kNoMusic;
+FMUSIC_MODULE *musicModule = NULL;
+
+void EnableMusic( MBoolean on )
+{
+	FMUSIC_SetMasterVolume( musicModule, on? 192: 0 );
+}
+
+void FastMusic( void )
+{
+	if( musicModule && !musicFast )
+	{
+		FMUSIC_SetMasterSpeed( musicModule, 1.3f );
+		musicFast = true;
+	}
+}
+
+void SlowMusic( void )
+{
+	if( musicModule && musicFast )
+	{
+		FMUSIC_SetMasterSpeed( musicModule, 1.0f );
+		musicFast = false;
+	}
+}
+
+void PauseMusic( void )
+{
+	if( musicSelection >= 0 && musicSelection <= kSongs )
+	{
+		FMUSIC_SetPaused( musicModule, true );
+		musicLevel++;
+	}
+}
+
+void ResumeMusic( void )
+{
+	if( musicSelection >= 0 && musicSelection <= kSongs )
+	{
+		musicLevel--;
+		FMUSIC_SetPaused( musicModule, false );
+	}
+}
+
+void ChooseMusic( short which )
+{
+	if( musicSelection >= 0 && musicSelection <= kSongs )	
+	{
+		FMUSIC_StopSong( musicModule );
+		FMUSIC_FreeSong( musicModule );
+		musicModule = NULL;
+	}
+
+	if( which >= 0 && which <= kSongs )
+	{
+		musicModule = FMUSIC_LoadSong( QuickResourceName( "mod", which+128, "" ) );		
+		if( musicModule != NULL )
+		{
+			FMUSIC_SetMasterVolume( musicModule, musicOn? 192: 0 );
+			FMUSIC_SetLooping( musicModule, true );
+			FMUSIC_PlaySong( musicModule );
+			musicSelection = which;
+			musicLevel     = 0;
+		}
+	}
+}
--- /dev/null
+++ b/Source/fmodsoundfx.cpp
@@ -1,0 +1,61 @@
+// soundfx.c
+
+#include "main.h"
+#include "soundfx.h"
+#include "music.h"
+
+#include "fmod.h"
+#include "fmod_errors.h"
+
+FSOUND_SAMPLE*  sound[kNumSounds];
+MBoolean        soundOn = true;
+
+void InitSound( void )
+{
+	int  index;
+	
+    if (!FSOUND_Init(44100, 64, FSOUND_INIT_USEDEFAULTMIDISYNTH))
+	{
+		musicOn = soundOn = false; 
+		return;
+	}
+	
+	for( index=0; index<kNumSounds; index++ )
+	{
+		sound[index] = FSOUND_Sample_Load( FSOUND_UNMANAGED, QuickResourceName( "snd", index+128, ".wav" ), FSOUND_NORMAL | FSOUND_LOOP_OFF | FSOUND_2D, 0 );
+		if( sound[index] == NULL )
+		{
+			Error( "InitSound: files are missing" );
+		}
+	}
+}
+
+
+void PlayMono( short which )
+{
+	if( soundOn )
+	{
+		int chanHandle = FSOUND_PlaySound( FSOUND_FREE, sound[which] );
+	}
+}
+
+void PlayStereo( short player, short which )
+{
+	PlayStereoFrequency( player, which, 0 );
+}
+
+void PlayStereoFrequency( short player, short which, short freq )
+{
+	if( soundOn )
+	{
+		int chanHandle = FSOUND_PlaySoundEx( FSOUND_FREE, sound[which], NULL, true );
+		FSOUND_SetPan( chanHandle, player? 255: 0 );
+		FSOUND_SetFrequency( chanHandle, (FSOUND_GetFrequency(chanHandle) * (16 + freq)) / 16 );
+	    FSOUND_SetPaused( chanHandle, false );
+	}
+}
+
+void UpdateSound()
+{
+	// no-op!
+}
--- /dev/null
+++ b/Source/font.cpp
@@ -1,0 +1,120 @@
+// font.c
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include "main.h"
+#include "font.h"
+#include "gworld.h"
+
+
+#define kNumFonts (picBatsuFont-picFont+1)
+
+static SkittlesFont s_font[kNumFonts] = {0};
+
+
+static SkittlesFontPtr LoadFont( SkittlesFontPtr font, int pictID, unsigned char *letterMap )
+{
+	unsigned char* lastLine;
+	unsigned char  white;
+	MBoolean       success = false;
+	int            start, across, skip;
+	SDL_Surface*   temporarySurface;
+	SDL_Rect       sdlRect;
+	
+	temporarySurface = LoadPICTAsSurface( pictID, 8 );
+	
+	if( temporarySurface )
+	{
+		sdlRect.x = 0;
+		sdlRect.y = 0;
+		sdlRect.h = temporarySurface->h;
+		sdlRect.w = temporarySurface->w;
+		
+		font->surface = SDLU_InitSurface( &sdlRect, 8 );
+		
+		SDLU_BlitSurface(  temporarySurface, &temporarySurface->clip_rect,
+		                   font->surface,    &temporarySurface->clip_rect  );
+		
+		SDL_FreeSurface( temporarySurface );
+		
+		white    = SDL_MapRGB( font->surface->format, 0xFF, 0xFF, 0xFF );
+		lastLine = (unsigned char*) font->surface->pixels + (font->surface->pitch * (font->surface->h - 1));
+		across   = 0;
+		
+		// Measure empty space between character breaks
+		while( lastLine[across] == white ) across++;
+		skip = across;
+		
+		success = true;
+
+		// Measure character starts and widths
+		while( *letterMap )
+		{
+			while( lastLine[across] != white ) across++;
+			if( across > font->surface->pitch ) 
+			{
+				success = false;
+				break;
+			}
+			
+			start = across;
+			font->across[*letterMap] = across + (skip/2);
+
+			while( lastLine[across] == white ) across++;		
+			font->width [*letterMap] = across - start - skip;
+
+			letterMap++;
+		}
+	}
+	
+	if( success )
+	{
+		return font;
+	}
+	else
+	{
+		Error( "LoadFont: files are missing or corrupt" );
+		return NULL;
+	}
+}
+
+
+void InitFont( void ) 
+{
+	LoadFont( &s_font[0],  picFont, (unsigned char*) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!:,.()*?0123456789'|-����� " );
+	LoadFont( &s_font[1],  picHiScoreFont, (unsigned char*) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*().,/-=_+<>?|'\":; " );
+	LoadFont( &s_font[2],  picContinueFont, (unsigned char*) "0123456789" );
+	LoadFont( &s_font[3],  picBalloonFont, (unsigned char*) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-=_+;:,./<>? ��'\"" );
+	LoadFont( &s_font[4],  picZapFont, (unsigned char*) "0123456789*PS" );
+	LoadFont( &s_font[5],  picZapOutlineFont, (unsigned char*) "0123456789*" );
+	LoadFont( &s_font[6],  picVictoryFont, (unsigned char*) "AB" );
+	LoadFont( &s_font[7],  picBubbleFont, (unsigned char*) "*" );
+	LoadFont( &s_font[8],  picTinyFont, (unsigned char*) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.! " );
+	LoadFont( &s_font[9],  picDashedLineFont, (unsigned char*) "." );
+	LoadFont( &s_font[10], picBatsuFont, (unsigned char*) "X" );
+}
+
+
+SkittlesFontPtr GetFont( int pictID )
+{
+	int fontID = pictID - picFont;
+	
+	if( (fontID < 0) || (fontID > kNumFonts) ) 
+		Error( "GetFont: fontID" );
+			
+	return &s_font[fontID];
+} 
+
+
+int GetTextWidth( SkittlesFontPtr font, const char *text )
+{
+	int width = 0;
+	while( *text )
+	{
+		width += font->width[*text++];
+	}
+	
+	return width;
+}
+
--- /dev/null
+++ b/Source/font.h
@@ -1,0 +1,25 @@
+// font.h
+
+
+#ifndef __FONT__
+#define __FONT__
+
+
+#include "SDL.h"
+
+
+typedef struct 
+{
+	SDL_Surface *surface;
+	int width[256];
+	int across[256];
+}
+SkittlesFont, *SkittlesFontPtr;
+
+
+void            InitFont( void );
+SkittlesFontPtr GetFont( int pictID );
+int             GetTextWidth( SkittlesFontPtr font, const char *text );
+
+
+#endif
--- /dev/null
+++ b/Source/gameticks.cpp
@@ -1,0 +1,45 @@
+// gameticks.c
+
+#include "SDL.h"
+#include "gameticks.h"
+
+unsigned long baseTickCount, freezeTickCount;
+int freezeLevel;
+
+unsigned long MTickCount()
+{
+	return (unsigned long) ((float)SDL_GetTicks() * 0.06f);
+}
+
+void InitGameTickCount( void )
+{
+	baseTickCount = freezeTickCount = 0;
+	freezeLevel = 0;
+}
+
+void FreezeGameTickCount( void )
+{
+	if( freezeLevel	== 0 ) 
+    {
+         freezeTickCount = MTickCount( );
+    }
+	freezeLevel--;
+}
+
+void UnfreezeGameTickCount( void )
+{
+	freezeLevel++;
+	if( freezeLevel >= 0 )
+	{
+		freezeLevel = 0;
+		baseTickCount += MTickCount( ) - freezeTickCount;
+	}
+}
+
+unsigned long GameTickCount( void )
+{
+	if( freezeLevel < 0 )
+		return freezeTickCount - baseTickCount;
+
+	return MTickCount( ) - baseTickCount;
+}
--- /dev/null
+++ b/Source/gameticks.h
@@ -1,0 +1,7 @@
+// gameticks.h
+
+unsigned long MTickCount();
+void InitGameTickCount( void );
+void FreezeGameTickCount( void );
+void UnfreezeGameTickCount( void );
+unsigned long GameTickCount( void );
--- /dev/null
+++ b/Source/graphics.cpp
@@ -1,0 +1,194 @@
+// graphics.c
+
+#include <stdlib.h>
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include "main.h"
+#include "players.h"
+#include "graphics.h"
+#include "gworld.h"
+#include "moving.h"
+#include "tweak.h"
+#include "gameticks.h"
+#include "blitter.h"
+#include "victory.h"
+#include "grays.h"
+#include "level.h"
+#include "keyselect.h"
+
+
+SDL_Surface* backdropSurface = NULL;
+
+
+void DrawSpriteBlobs( int player, int type )
+{
+	MRect firstRect, secondRect, thirdRect;
+	const int repeat = 0xFF, forever = 0xFE;
+	
+	static const unsigned char blobAnimation[6][2][25] = 
+	{ 
+	    { { kNoSuction,       kNoSuction,       kNoSuction,       kNoSuction,
+	        kNoSuction,       kNoSuction,       kNoSuction,       kNoSuction,
+	        kNoSuction,       kNoSuction,       kNoSuction,       kNoSuction,       
+		    kFlashBrightBlob, kFlashBrightBlob, kFlashBrightBlob, kFlashBrightBlob,
+		    kFlashBrightBlob, kFlashBrightBlob, kFlashBrightBlob, kFlashBrightBlob,
+		    kFlashBrightBlob, kFlashBrightBlob, kFlashBrightBlob, kFlashBrightBlob, repeat }, 
+		  { kNoSuction,       kNoSuction,       kNoSuction,       kNoSuction,
+		    kNoSuction,       kNoSuction,       kNoSuction,       kNoSuction,
+		    kNoSuction,       kNoSuction,       kNoSuction,       kNoSuction,
+		    kNoSuction,       kNoSuction,       kNoSuction,       kNoSuction,
+		    kNoSuction,       kNoSuction,       kNoSuction,       kNoSuction,
+		    kNoSuction,       kNoSuction,       kNoSuction,       kNoSuction, repeat } },
+		{ { kNoSuction,       kSquish,          kNoSuction,       kSquash,        
+		    kNoSuction,       kSquish,          kNoSuction,       kSquash,
+		    kNoSuction,       forever },
+		  { kNoSuction,       kSquish,          kNoSuction,       kSquash,        
+		    kNoSuction,       kSquish,          kNoSuction,       kSquash,
+		    kNoSuction,       forever } },
+		{ { kSobBlob,         kSobBlob,         kSobBlob,         kSobBlob,         
+			kSob2Blob,        kSob2Blob,        kSob2Blob,        kSob2Blob,        
+			repeat },
+		  { kSobBlob,         kSobBlob,         kSobBlob,         kSobBlob,         
+			kSob2Blob,        kSob2Blob,        kSob2Blob,        kSob2Blob,        
+			repeat } },
+		{ { kBombFuse1,       kBombFuse2,       kBombFuse3,       repeat }, 
+		  { kBombFuse1,       kBombFuse2,       kBombFuse3,       repeat } }, 
+		{ { kBlinkBomb1,      kBombFuse2,       kBlinkBomb3,      kBombFuse1,        
+		    kBlinkBomb2,      kBombFuse3,       repeat },
+		  { kBlinkBomb1,      kBombFuse2,       kBlinkBomb3,      kBombFuse1,        
+		    kBlinkBomb2,      kBombFuse3,       repeat } }
+	};
+	
+	if( grenade[player] ) type += 3;
+	
+	SDLU_AcquireSurface( playerSpriteSurface[player] );
+	
+	if( blobAnimation[type][0][anim[player]] == forever ) anim[player]--;
+	if( blobAnimation[type][0][anim[player]] == repeat  ) anim[player] = 0;
+	
+	CalcBlobRect( blobX[player], blobY[player], &firstRect );
+	if( halfway[player] ) OffsetMRect( &firstRect, 0, kBlobVertSize / 2 );
+	
+	TweakFirstBlob ( player, &firstRect );
+	secondRect = firstRect;
+	TweakSecondBlob( player, &secondRect );
+		
+	thirdRect = firstRect;
+	thirdRect.top    -= kBlobShadowError;
+	thirdRect.left   -= kBlobShadowError;
+	thirdRect.right  += kBlobShadowDepth + kBlobShadowError;
+	thirdRect.bottom += kBlobShadowDepth + kBlobShadowError;
+	CleanSpriteArea( player, &thirdRect );
+							
+	thirdRect = secondRect;
+	thirdRect.top    -= kBlobShadowError;
+	thirdRect.left   -= kBlobShadowError;
+	thirdRect.right  += kBlobShadowDepth + kBlobShadowError;
+	thirdRect.bottom += kBlobShadowDepth + kBlobShadowError;
+	CleanSpriteArea( player, &thirdRect );
+	
+	thirdRect = firstRect;
+	OffsetMRect( &thirdRect, shadowDepth[player], shadowDepth[player] );
+	SurfaceDrawShadow( &thirdRect,  colorA[player], blobAnimation[type][0][anim[player]] );
+
+	thirdRect = secondRect;
+	OffsetMRect( &thirdRect, shadowDepth[player], shadowDepth[player] );
+	SurfaceDrawShadow( &thirdRect, colorB[player], blobAnimation[type][1][anim[player]] );
+
+	SurfaceDrawSprite( &firstRect,  colorA[player], blobAnimation[type][0][anim[player]] );
+	
+	SurfaceDrawSprite( &secondRect, colorB[player], blobAnimation[type][1][anim[player]] );
+	
+	SDLU_ReleaseSurface( playerSpriteSurface[player] );
+}
+
+void CleanSpriteArea( int player, MRect *myRect )
+{
+	SDL_Rect sdlRect;
+
+	SDLU_MRectToSDLRect( myRect, &sdlRect );
+	
+	SDLU_BlitSurface( playerSurface[player],       &sdlRect,
+	                  playerSpriteSurface[player], &sdlRect  );
+		
+	SetUpdateRect( player, myRect );
+}
+
+void EraseSpriteBlobs( int player )
+{
+	MRect myRect, secondRect;
+	
+	CalcBlobRect( blobX[player], blobY[player], &myRect );
+	if( halfway[player] ) OffsetMRect( &myRect, 0, kBlobVertSize / 2 );
+
+	TweakFirstBlob( player, &myRect );
+	secondRect = myRect;
+	secondRect.top    -= kBlobShadowError;
+	secondRect.left   -= kBlobShadowError;
+	secondRect.right  += kBlobShadowDepth + kBlobShadowError;
+	secondRect.bottom += kBlobShadowDepth + kBlobShadowError;
+	CleanSpriteArea( player, &secondRect );
+
+	TweakSecondBlob( player, &myRect );
+	myRect.top    -= kBlobShadowError;
+	myRect.left   -= kBlobShadowError;
+	myRect.right  += kBlobShadowDepth + kBlobShadowError;
+	myRect.bottom += kBlobShadowDepth + kBlobShadowError;
+	CleanSpriteArea( player, &myRect );
+}
+
+void CalcBlobRect( int x, int y, MRect *myRect )
+{
+	myRect->top = y * kBlobVertSize;
+	myRect->left = x * kBlobHorizSize;
+	myRect->bottom = myRect->top + kBlobVertSize;
+	myRect->right = myRect->left + kBlobHorizSize;
+}
+
+void InitBackdrop( void )
+{
+	backdropSurface = LoadPICTAsSurface( picBackdrop, 16 );
+}
+
+void DrawBackdrop( void )
+{
+	SDL_Rect backdropRect = { 0, 0, 640, 480 };
+	
+	SDLU_BlitFrontSurface( backdropSurface, &backdropRect, &backdropRect );
+}
+
+void OpeningProgress( int current, int total ) 
+{
+	// computers are fast now... progress bar seems silly
+	current, total;
+}
+
+void ShowTitle( void )
+{
+	int time;
+	
+	DrawPICTInSurface( frontSurface, picTitle );
+	
+	SDL_Flip( frontSurface );
+	
+	QuickFadeIn( NULL );	
+
+	time = MTickCount() + 180;
+
+	RetrieveResources( );
+	
+	while( time > MTickCount() && !SDLU_Button() )
+	{
+		SDLU_Yield();
+	}
+	
+	WaitForRelease();
+		
+	QuickFadeOut( NULL );
+	
+	SDL_FillRect( frontSurface, &frontSurface->clip_rect, SDL_MapRGB( frontSurface->format, 0, 0, 0 ) );
+	SDL_Flip( frontSurface );
+}
+
--- /dev/null
+++ b/Source/graphics.h
@@ -1,0 +1,25 @@
+// graphics.h
+
+
+#include "SDL.h"
+
+
+void DrawSpriteBlobs( int player, int type );
+void EraseSpriteBlobs( int player );
+void CleanSpriteArea( int player, MRect *myRect );
+void CalcBlobRect( int x, int y, MRect *myRect );
+void DrawBackdrop( void );
+void ShowTitle( void );
+void InitBackdrop( void );
+void OpeningProgress( int current, int total ) ;
+
+
+extern SDL_Surface* backdropSurface;
+
+
+enum 
+{
+	blobBlinkAnimation = 0,
+	blobJiggleAnimation,
+	blobCryAnimation
+};
--- /dev/null
+++ b/Source/graymonitor.cpp
@@ -1,0 +1,91 @@
+// graymonitor.c
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include "main.h"
+#include "graymonitor.h"
+#include "grays.h"
+#include "score.h"
+#include "gworld.h"
+#include "graphics.h"
+#include "level.h"
+
+
+static SDL_Surface* smallGrayDrawSurface;
+
+
+MRect grayMonitorZRect, grayMonitorRect[2];
+MBoolean grayMonitorVisible[2] = {true, true};
+
+void InitGrayMonitors( void )
+{
+	const double windowLoc[ ] = { 0.16, 0.84 };
+	SDL_Rect     sdlRect;
+			 
+	grayMonitorZRect.top = grayMonitorZRect.left = 0;
+	grayMonitorZRect.bottom = 32; grayMonitorZRect.right = 144;
+	
+	grayMonitorRect[0] = grayMonitorRect[1] = grayMonitorZRect;
+	CenterRectOnScreen( &grayMonitorRect[0], windowLoc[0], 0.11 );
+	CenterRectOnScreen( &grayMonitorRect[1], windowLoc[1], 0.11 );
+
+	smallGrayDrawSurface = SDLU_InitSurface( SDLU_MRectToSDLRect( &grayMonitorZRect, &sdlRect ), 16 );
+	DrawPICTInSurface( smallGrayDrawSurface, picBoard );
+}
+
+void ShowGrayMonitor( short player )
+{
+	SDL_Rect   sourceSDLRect, destSDLRect;
+	short      monitor;
+	MRect      myRect = { 4, 4, kBlobVertSize+4, 4 };
+	MRect      srcRect;
+	const int  smallGrayList[] = { 0, kSmallGray1, kSmallGray2, kSmallGray3, kSmallGray4, kSmallGray5 };
+	
+	if( !grayMonitorVisible[player] ) return;
+	
+	if( control[player] != kNobodyControl )
+	{
+		SDLU_AcquireSurface( smallGrayDrawSurface );
+		
+		SDLU_BlitSurface( boardSurface[player], &smallGrayDrawSurface->clip_rect,
+						  smallGrayDrawSurface, &smallGrayDrawSurface->clip_rect  );
+	 				
+		monitor = unallocatedGrays[player];
+		
+		CalcBlobRect( kSobBlob, 3, &srcRect );
+		while( monitor >= (6*4) )
+		{
+			myRect.right += kBlobHorizSize;
+			SurfaceDrawSprite( &myRect, 4, kSobBlob );
+			myRect.left = myRect.right;
+			
+			monitor -= (6*4);
+		}
+		
+		CalcBlobRect( kNoSuction, kGray-1, &srcRect );
+		while( monitor >= 6 )
+		{
+			myRect.right += kBlobHorizSize;
+			SurfaceDrawAlpha( &myRect, kGray, kLight, kGrayNoBlink );
+			myRect.left = myRect.right;
+			
+			monitor -= 6;
+		}
+		
+		if( monitor > 0 )
+		{
+			myRect.right += kBlobHorizSize;
+			SurfaceDrawAlpha( &myRect, kGray, kLight, smallGrayList[monitor] );
+			myRect.left = myRect.right;
+			myRect.right += kBlobHorizSize;
+			SurfaceDrawAlpha( &myRect, kGray, kLight, smallGrayList[monitor]+1 );
+		}
+		
+		SDLU_ReleaseSurface( smallGrayDrawSurface );
+
+		SDLU_BlitFrontSurface( smallGrayDrawSurface, 
+		                       SDLU_MRectToSDLRect( &grayMonitorZRect, &sourceSDLRect ),
+		                       SDLU_MRectToSDLRect( &grayMonitorRect[player], &destSDLRect ) );
+	}
+}
--- /dev/null
+++ b/Source/graymonitor.h
@@ -1,0 +1,7 @@
+// graymonitor.h
+
+void InitGrayMonitors( void );
+void ShowGrayMonitor( short player );
+
+extern MRect grayMonitorZRect, grayMonitorRect[2];
+extern MBoolean grayMonitorVisible[2];
--- /dev/null
+++ b/Source/grays.cpp
@@ -1,0 +1,378 @@
+// grays.c
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include "main.h"
+#include "grays.h"
+#include "players.h"
+#include "graphics.h"
+#include "gworld.h"
+#include "control.h"
+#include "soundfx.h"
+#include "score.h"
+#include "random.h"
+#include "graymonitor.h"
+#include "gameticks.h"
+#include "blitter.h"
+#include "zap.h"
+#include "level.h"
+#include "opponent.h"
+
+int grays[2][kGridAcross], grayAir[2][kGridAcross];
+int unallocatedGrays[2], lockGrays[2], rowBounce[2][kGridAcross], splat[2][kGridAcross];
+int blinkTime[2], blinkStage[2];
+const int blinkList[] = { kGrayNoBlink, kGrayBlink1,  kGrayBlink2, 
+  						  kGrayBlink3,  kGrayBlink2,  kGrayBlink1   };
+
+void InitGrays( void )
+{
+	int player, gridX;
+	
+	blinkTime[0] = GameTickCount( );
+	blinkTime[1] = GameTickCount( ) + 60;
+	
+	for( player=0; player<=1; player++ )
+	{
+		blinkStage[player] = 0;
+		unallocatedGrays[player] = 0;
+		
+		for( gridX=0; gridX<kGridAcross; gridX++ )
+		{
+			grays[player][gridX] = 0;
+			rowBounce[player][gridX] = -1;
+		}
+	}	
+}
+
+void BlinkGrays( int player )
+{
+	int x, y, currentBlinkGraphic;
+	MRect myRect;
+	
+	if( blinkTime[player] > GameTickCount( ) )
+		return;
+	
+	blinkTime[player] += 2;
+	blinkStage[player]++;
+	
+	if( blinkStage[player] > 5 )
+	{
+		blinkStage[player] = 0;
+		blinkTime[player] = GameTickCount( ) + kTimeBetweenBlinks;
+	}
+	
+	if( flashyAnimation )
+	{
+		currentBlinkGraphic = blinkList[ blinkStage[player] ];
+	
+		SDLU_AcquireSurface( playerSurface[player] );
+		
+		for( x=0; x<kGridAcross; x++ )
+		{
+			if( rowBounce[player][x] == -1 )
+			{
+				for( y=kGridDown-1; y>=0; y-- )
+				{			
+					if( (grid[player][x][y] == kGray) &&
+						(suction[player][x][y] == kGrayNoBlink) )
+					{
+						CalcBlobRect( x, y, &myRect );
+						SurfaceDrawBoard( player, &myRect );
+						SurfaceDrawAlpha( &myRect, kGray, kLight, currentBlinkGraphic );
+						CleanSpriteArea( player, &myRect );
+					}					
+				}
+			}
+		}
+		
+		SDLU_ReleaseSurface( playerSurface[player] );
+	}
+}
+
+void CalculateGrays( int player, int blobsToDrop )
+{
+	if( blobsToDrop < unallocatedGrays[1-player] )
+	{
+		unallocatedGrays[1-player] -= blobsToDrop;
+		lockGrays[1-player] -= blobsToDrop;
+		if( lockGrays[1-player] < 0 ) lockGrays[1-player] = 0;
+		
+		blobsToDrop = 0;
+		ShowGrayMonitor( 1-player );
+	}
+	else
+	{
+		blobsToDrop -= unallocatedGrays[1-player];
+		unallocatedGrays[1-player] = 0;
+		lockGrays[1-player] = 0;
+		ShowGrayMonitor( 1-player );
+		
+		unallocatedGrays[player] += blobsToDrop;
+		ShowGrayMonitor( player );
+	}
+}
+
+void LockGrays( int player )
+{
+	lockGrays[player] = unallocatedGrays[player];
+}
+
+void SetupGrays( int player )
+{
+	int grayX, change;
+	MBoolean onlyOnce[kGridAcross];
+	int rowFree[kGridAcross];
+	
+	if( role[player] == kDropGrays ) return; // next time around
+	
+	for( grayX=0; grayX < kGridAcross; grayX++ )
+	{
+		grayAir[player][grayX] = -RandomBefore(kBlobVertSize*3/2);
+		rowFree[grayX] = -1;
+		
+		while( grid[player][grayX][rowFree[grayX]+1] == kEmpty && 
+			   rowFree[grayX] < (kGridDown-1) )
+			rowFree[grayX]++;
+	}
+	
+	while( lockGrays[player] >= kGridAcross )
+	{
+		change = 0;
+		
+		for( grayX=0; grayX < kGridAcross; grayX++ )
+		{
+			if( rowFree[grayX] >= 0 )
+			{
+				grays[player][grayX]++;
+				//grayAir[player][grayX] -= kBlobVertSize;
+				rowFree[grayX]--;
+				change++;
+			}
+		}
+		
+		lockGrays[player] -= change;
+		unallocatedGrays[player] -= change;
+		
+		if( change == 0 ) break;
+	}
+	
+	if( lockGrays[player] > 0 )
+	{
+		for( grayX = 0; grayX < kGridAcross; grayX++ )
+		{
+			onlyOnce[grayX] = rowFree[grayX] > 0;
+		}
+		
+		while( (onlyOnce[0] || onlyOnce[1] || onlyOnce[2] ||
+			    onlyOnce[3] || onlyOnce[4] || onlyOnce[5]) &&
+			   (lockGrays[player] > 0) )
+		{
+			grayX = RandomBefore(kGridAcross);
+			
+			if( onlyOnce[grayX] )
+			{
+				grays[player][grayX]++;
+				//grayAir[player][grayX] -= kBlobVertSize;
+				lockGrays[player]--;
+				unallocatedGrays[player]--;
+				onlyOnce[grayX] = false;
+			}
+		}
+	}
+}
+
+void DropGrays( int player )
+{
+	MRect myRect;
+	int count, grayX;
+	
+	if( blobTime[player] > GameTickCount( ) )
+		return;
+		
+	blobTime[player]++;
+	
+	for( grayX = 0; grayX < kGridAcross; grayX++ )
+	{
+		if( grays[player][grayX] > 0 )
+		{
+			myRect.bottom = grayAir[player][grayX];
+			myRect.left = kBlobHorizSize * grayX;
+			myRect.top = myRect.bottom - (kBlobVertSize * grays[player][grayX]);
+			myRect.right = myRect.left + kBlobHorizSize;
+			CleanSpriteArea( player, &myRect );
+			
+			grayAir[player][grayX] += 4;
+			if( grayAir[player][grayX] > 0 ) grayAir[player][grayX] += grayAir[player][grayX] / 12;
+			
+			if( ( grayAir[player][grayX] / kBlobVertSize ) >= GetRowHeight( player, grayX ) )
+			{
+				PlaceGrayRow( player, grayX );
+				ShowGrayMonitor( player );
+			}
+			else
+			{
+				myRect.top = grayAir[player][grayX];
+				myRect.bottom = myRect.top + kBlobVertSize;
+				
+				SDLU_AcquireSurface( playerSpriteSurface[player] );
+
+				for( count=0; count<grays[player][grayX]; count++ )
+				{
+					OffsetMRect( &myRect, 0, -kBlobVertSize );
+					CleanSpriteArea( player, &myRect );
+					SurfaceDrawAlpha( &myRect, kGray, kLight, kGrayNoBlink );					
+				}
+
+				SDLU_ReleaseSurface( playerSpriteSurface[player] );
+			}
+		}
+	}
+	
+	Bounce( player );
+		
+	if( !BusyDroppingGrays( player ) )
+	{
+		role[player] = kWaitForRetrieval;
+	}
+}
+
+MBoolean BusyDroppingGrays( int player )
+{
+	int grayX;
+	
+	for( grayX = 0; grayX<kGridAcross; grayX++ )
+	{
+		if( rowBounce[player][grayX] >= 0 ||
+			grays[player][grayX] > 0 ) 
+		{
+			return true;
+		} 
+	}
+
+	return false;
+}
+
+void PlaceGrayRow( int player, int grayX )
+{
+	int grayY;
+	MRect myRect;
+
+	if( player == 1 ) OpponentPissed( );
+	
+	rowBounce[player][grayX] = 0;
+	splat[player][grayX] = GetRowHeight( player, grayX )+1;
+	
+	SDLU_AcquireSurface( playerSurface[player] );
+	
+	while( grays[player][grayX] > 0 )
+	{
+		grayY = GetRowHeight( player, grayX );
+				
+		grid[player][grayX][grayY] = kGray;
+		suction[player][grayX][grayY] = kGrayNoBlink;
+		
+		CalcBlobRect( grayX, grayY, &myRect );
+		SurfaceDrawAlpha( &myRect, kGray, kLight, kGrayNoBlink );
+		CleanSpriteArea( player, &myRect );
+		
+		grays[player][grayX]--;
+	}
+	
+	SDLU_ReleaseSurface( playerSurface[player] );
+	
+	PlayStereoFrequency( player, kBounce, player );
+}
+
+void Bounce( int player )
+{
+	int x, y, bounce, suck, blob, rows, currentBlinkGraphic;
+	MRect blobRect;
+	double blobTop, compress;
+	const double compressList[kZapFrames+1] = { 
+							 -kBlobVertSize + 0.83,
+							 -kBlobVertSize + 1.58,
+							 -kBlobVertSize + 2.22,
+							 -kBlobVertSize + 2.76,
+							 -kBlobVertSize + 3.20,
+							 -kBlobVertSize + 3.55, 
+							 -kBlobVertSize + 3.80,
+							 -kBlobVertSize + 3.95,  
+							 -kBlobVertSize + 4.00,
+							 -kBlobVertSize + 3.95,  
+							 -kBlobVertSize + 3.80,
+							 -kBlobVertSize + 3.55, 
+							 -kBlobVertSize + 3.20,
+							 -kBlobVertSize + 2.76,
+							 -kBlobVertSize + 2.22,
+							 -kBlobVertSize + 1.58,
+							 -kBlobVertSize + 0.83,
+							 -kBlobVertSize,
+							 -kBlobVertSize,
+							 -kBlobVertSize,
+							 -kBlobVertSize
+						   };
+	
+	if( flashyAnimation )
+	{
+		currentBlinkGraphic = blinkList[ blinkStage[player] ];
+		
+		SDLU_AcquireSurface( playerSpriteSurface[player] );
+		
+		for( x=0; x<kGridAcross; x++ )
+		{
+			if( rowBounce[player][x] >= 0 ) CleanSplat( player, x, splat[player][x], rowBounce[player][x] );
+		}
+		
+		for( x=0; x<kGridAcross; x++ )
+		{
+			bounce = rowBounce[player][x];
+			if( bounce >= 0 )
+			{
+				compress = compressList[bounce];
+				rows = GetRowHeight( player, x );
+				
+				CalcBlobRect( x, rows, &blobRect );
+				blobRect.bottom = kBlobVertSize * kGridDown;
+				SurfaceDrawBoard( player, &blobRect );
+				SetUpdateRect( player, &blobRect );
+				
+				blobRect.top = kBlobVertSize * (kGridDown-1);
+				blobTop = kBlobVertSize * (kGridDown-1);
+				
+				for( y=kGridDown-1; y>=rows; y-- )
+				{
+					suck = suction[player][x][y];
+					blob = grid[player][x][y];
+					if( suck == kNoSuction && blob >= kFirstBlob &&
+						blob <= kLastBlob && compress > (-kBlobVertSize + 3) )
+					{
+						suck = kSquish;
+					}
+					
+					if( blob == kGray )	SurfaceDrawAlpha( &blobRect, kGray, kLight, currentBlinkGraphic );
+					else SurfaceDrawBlob( player, &blobRect, blob, suck, charred[player][x][y] );
+					
+					blobTop += compress;
+					blobRect.top = (short) blobTop;
+					blobRect.bottom = blobRect.top + kBlobVertSize;
+				}
+			}
+		}
+
+		for( x=0; x<kGridAcross; x++ )
+		{
+			if( rowBounce[player][x] >= 0 ) DrawSplat( player, x, splat[player][x], rowBounce[player][x] );
+		}
+		
+		SDLU_ReleaseSurface( playerSpriteSurface[player] );
+	}
+	
+	for( x=0; x<kGridAcross; x++ )
+	{
+		if( rowBounce[player][x] >= 0 && rowBounce[player][x] < (kZapFrames) )
+			rowBounce[player][x]++;
+		else
+			rowBounce[player][x] = -1;
+	}
+}
--- /dev/null
+++ b/Source/grays.h
@@ -1,0 +1,17 @@
+// grays.h
+
+void InitGrays( void );
+void CalculateGrays( int player, int BlobsToDrop );
+void SetupGrays( int player );
+void DropGrays( int player );
+void BlinkGrays( int player );
+void PlaceGrayRow( int player, int grayX );
+void Bounce( int player );
+void LockGrays( int player );
+MBoolean BusyDroppingGrays( int player );
+
+extern int grays[2][kGridAcross], grayAir[2][kGridAcross], graySpeed[2];
+extern int unallocatedGrays[2], lockGrays[2], rowBounce[2][kGridAcross], splat[2][kGridAcross];
+extern int blinkTime[2], sunTime[2], blinkStage[2], sunStage[2];
+
+#define kTimeBetweenBlinks 120
--- /dev/null
+++ b/Source/gworld.cpp
@@ -1,0 +1,227 @@
+// gworld.c
+
+#include "SDL.h"
+#include "SDLU.h"
+#include "SDL_image.h"
+
+#include "main.h"
+#include "gworld.h"
+#include "blitter.h"
+#include "graphics.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+SDL_Surface* blobSurface;
+SDL_Surface* maskSurface;
+SDL_Surface* charMaskSurface;
+SDL_Surface* boardSurface[2];
+SDL_Surface* blastSurface;
+SDL_Surface* blastMaskSurface;
+SDL_Surface* playerSurface[2];
+SDL_Surface* playerSpriteSurface[2];
+
+
+void GetBlobGraphics()
+{
+	MRect myRect;
+	
+	// Get board
+	
+	myRect.top = myRect.left = 0;
+	myRect.right = kBlobHorizSize * kGridAcross;
+	myRect.bottom = kBlobVertSize * (kGridDown-1);
+	
+	boardSurface[0] = LoadPICTAsSurface( picBoard, 16 );
+	
+	boardSurface[1] = LoadPICTAsSurface( picBoardRight, 16 );
+	if( boardSurface[1] == NULL )
+		boardSurface[1] = LoadPICTAsSurface( picBoard, 16 );
+	
+	// Get blob worlds
+
+	blobSurface = LoadPICTAsSurface( picBlob, 16 );
+	maskSurface = LoadPICTAsSurface( picBlobMask, 1 );
+	charMaskSurface = LoadPICTAsSurface( picCharMask, 1 );
+
+	// Get blast worlds
+	
+	blastSurface = LoadPICTAsSurface( picBlast, 16 );
+	blastMaskSurface = LoadPICTAsSurface( picBlastMask, 16 );
+}
+
+
+void InitPlayerWorlds()
+{
+	MRect     myRect;
+	SDL_Rect  sdlRect;
+	int       count;
+	
+	myRect.top = myRect.left = 0;
+	myRect.right = kGridAcross * kBlobHorizSize;
+	myRect.bottom = kGridDown * kBlobVertSize;
+	
+	SDLU_MRectToSDLRect( &myRect, &sdlRect );
+	
+	for( count=0; count<=1; count++ )
+	{
+		playerSurface[count]       = SDLU_InitSurface( &sdlRect, 16 );
+		playerSpriteSurface[count] = SDLU_InitSurface( &sdlRect, 16 );
+	}
+}
+
+
+void SurfaceDrawBoard( int player, const MRect *myRect )
+{
+	MRect    srcRect, offsetRect;
+	SDL_Rect srcSDLRect, offsetSDLRect;
+	
+	srcRect = *myRect;
+	if( srcRect.bottom <= kBlobVertSize ) return;
+	if( srcRect.top < kBlobVertSize ) srcRect.top = kBlobVertSize;
+
+	offsetRect = srcRect;
+	OffsetMRect( &offsetRect, 0, -kBlobVertSize );
+
+	SDLU_BlitSurface( boardSurface[player],     SDLU_MRectToSDLRect( &offsetRect, &offsetSDLRect ),
+	                  SDLU_GetCurrentSurface(), SDLU_MRectToSDLRect( &srcRect, &srcSDLRect )         );
+}
+
+
+void SurfaceDrawBlob( int player, const MRect *myRect, int blob, int state, int charred )
+{
+	SurfaceDrawBoard( player, myRect );
+	SurfaceDrawSprite( myRect, blob, state );
+
+	if( charred & 0x0F )
+	{
+		MRect blobRect, charRect, alphaRect;
+		
+		CalcBlobRect( (charred & 0x0F), kBombTop-1, &charRect );
+		CalcBlobRect( (charred & 0x0F), kBombBottom-1, &alphaRect );
+		CalcBlobRect( state, blob-1, &blobRect );
+
+		SurfaceBlitWeightedDualAlpha(  SDLU_GetCurrentSurface(),  blobSurface,  charMaskSurface,  blobSurface,  SDLU_GetCurrentSurface(),
+                                       myRect,                   &charRect,    &blobRect,        &alphaRect,    myRect, 
+                                      (charred & 0xF0)>>3 );
+	}
+}
+
+void SurfaceDrawShadow( const MRect *myRect, int blob, int state )
+{
+	int x;
+	MPoint offset[4] = { {-2, 0}, {0, -2}, {2, 0}, {0, 2} };
+	
+	if( blob > kEmpty )
+	{
+		MRect blobRect, destRect;
+
+		for( x=0; x<4; x++ )
+		{
+			destRect = *myRect;
+			OffsetMRect( &destRect, offset[x].h, offset[x].v );
+			
+			CalcBlobRect( state, blob-1, &blobRect );
+			SurfaceBlitColor(  maskSurface,  SDLU_GetCurrentSurface(),
+			                  &blobRect,    &destRect, 
+			                   0, 0, 0, 3 );
+		}
+	}
+}
+
+void SurfaceDrawColor( const MRect *myRect, int blob, int state, int r, int g, int b, int w )
+{
+	MRect blobRect;
+	if( blob > kEmpty )
+	{
+		CalcBlobRect( state, blob-1, &blobRect );
+		SurfaceBlitColor(  charMaskSurface,  SDLU_GetCurrentSurface(),
+						  &blobRect,         myRect, 
+						   r, g, b, w );
+	}
+}
+
+void SurfaceDrawAlpha( const MRect *myRect, int blob, int mask, int state )
+{
+	if( blob > kEmpty )
+	{
+		MRect blobRect, alphaRect;
+
+		CalcBlobRect( state, blob-1, &blobRect );
+		CalcBlobRect( state, mask-1, &alphaRect );
+		
+		SurfaceBlitAlpha( SDLU_GetCurrentSurface(),  blobSurface,  blobSurface, SDLU_GetCurrentSurface(),
+		                  myRect,                   &blobRect,    &alphaRect,   myRect );
+	}
+}
+
+void SurfaceDrawSprite( const MRect *myRect, int blob, int state )
+{
+	MRect blobRect;
+	if( blob > kEmpty )
+	{
+		CalcBlobRect( state, blob-1, &blobRect );
+		SurfaceBlitBlob( &blobRect, myRect );
+	}
+}
+
+
+MBoolean PICTExists( int pictID )
+{
+	if( FileExists( QuickResourceName( "PICT", pictID, ".jpg" ) ) )
+		return true;
+
+	if( FileExists( QuickResourceName( "PICT", pictID, ".png" ) ) )
+		return true;
+	
+	return false;
+}
+
+
+SDL_Surface* LoadPICTAsSurface( int pictID, int depth )
+{
+	const char*  filename;
+	SDL_Surface* surface;
+	
+	filename = QuickResourceName( "PICT", pictID, ".jpg" );
+	if( FileExists( filename ) )
+	{
+		surface = IMG_Load( filename );
+	}
+	else
+	{
+		filename = QuickResourceName( "PICT", pictID, ".png" );
+		if( FileExists( filename ) )
+		{
+			surface = IMG_Load( filename );
+		}
+		else
+		{
+			// Fail
+			return NULL;
+		}
+	}
+	
+	if( depth != 0 )
+	{
+		SDLU_ChangeSurfaceDepth( &surface, depth );
+	}
+	
+	return surface;
+}
+
+void DrawPICTInSurface( SDL_Surface* surface, int pictID )
+{
+	SDL_Surface* image;
+	
+	image = LoadPICTAsSurface( pictID, 0 );
+	if( image != NULL )
+	{
+		SDLU_BlitSurfaceHQ( image,    &image->clip_rect,
+		                    surface,  &surface->clip_rect );
+		                 
+		SDL_FreeSurface( image );
+	}
+}
--- /dev/null
+++ b/Source/gworld.h
@@ -1,0 +1,37 @@
+// gworld.h
+
+
+#include "SDL.h"
+
+
+#define kBlobHorizSize 24
+#define kBlobVertSize 24
+#define kBlobShadowDepth 6
+#define kBlobShadowError 2
+
+#define kBlastWidth 72
+#define kBlastHeight 72
+#define kBlastFrames 14
+
+void GetBlobGraphics();
+void InitPlayerWorlds();
+
+void         DrawPICTInSurface( SDL_Surface* surface, int pictID );
+SDL_Surface* LoadPICTAsSurface( int pictID, int depth );
+MBoolean      PICTExists( int pictID );
+
+void SurfaceDrawBoard( int player, const MRect *myRect );
+void SurfaceDrawShadow( const MRect *myRect, int blob, int state );
+void SurfaceDrawAlpha( const MRect *myRect, int blob, int mask, int state );
+void SurfaceDrawColor( const MRect *myRect, int blob, int state, int r, int g, int b, int w );
+void SurfaceDrawBlob( int player, const MRect *myRect, int blob, int state, int charred );
+void SurfaceDrawSprite( const MRect *myRect, int blob, int state );
+
+extern SDL_Surface* blobSurface;
+extern SDL_Surface* maskSurface;
+extern SDL_Surface* charMaskSurface;
+extern SDL_Surface* boardSurface[2];
+extern SDL_Surface* blastSurface;
+extern SDL_Surface* blastMaskSurface;
+extern SDL_Surface* playerSurface[2];
+extern SDL_Surface* playerSpriteSurface[2];
--- /dev/null
+++ b/Source/hiscore.cpp
@@ -1,0 +1,493 @@
+// hiscore.c
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "main.h"
+#include "gworld.h"
+#include "graphics.h"
+#include "score.h"
+#include "hiscore.h"
+#include "keyselect.h"
+#include "font.h"
+#include "blitter.h"
+#include "random.h"
+#include "pause.h"
+#include "level.h"
+#include "tutorial.h"
+#include "graymonitor.h"
+#include "players.h"
+#include "gameticks.h"
+#include "music.h"
+#include "soundfx.h"
+
+Combo defaultBest = 
+{
+	/*bestGrid[kGridAcross][kGridDown] = */ 
+	{ { kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty },
+	  { kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty },
+	  { kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty,                          1, 1, 1, 2, 2 },
+	  { kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty,           1, 1 },
+	  { kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty },
+	  { kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty } },
+
+	/*bestA      = */ 2,
+	/*bestB      = */ 2,
+	/*bestM      = */ false, 
+	/*bestG      = */ false, 
+	/*bestLv     = */ kTutorialLevel,
+	/*bestX      = */ 1,
+	/*bestR      = */ upRotate,
+	/*bestPlayer = */ 0,
+	/*bestValue  = */ (40*1) + (50*9),
+	/*bestName   = */ "Tutorial"
+};
+
+Combo best = 
+{
+	/*bestGrid[kGridAcross][kGridDown] = */ 
+	{ { kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty },
+	  { kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty },
+	  { kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty,                          1, 1, 1, 2, 2 },
+	  { kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty,           1, 1 },
+	  { kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty },
+	  { kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty } },
+
+	/*bestA      = */ 2,
+	/*bestB      = */ 2,
+	/*bestM      = */ false, 
+	/*bestG      = */ false, 
+	/*bestLv     = */ kTutorialLevel,
+	/*bestX      = */ 1,
+	/*bestR      = */ upRotate,
+	/*bestPlayer = */ 0,
+	/*bestValue  = */ (40*1) + (50*9),
+	/*bestName   = */ "Tutorial"
+};
+
+Combo evenBetter = {0};
+Combo potentialCombo[2];
+
+AutoPattern hiScorePattern[] =
+{
+	{ kIdleTicks,          60, 0,   NULL },	
+	{ kBlockUntilDrop,     0,  0,   NULL },	
+	{ kBlockUntilComplete, 0,  0,   NULL },	
+	{ kIdleTicks,          60, 0,   NULL },	
+	{ kComplete,           0,  0,   NULL }
+};
+
+HighScore scores[10] = 
+{	
+	{"Leviathan", 40000},
+	{"Dr. Crisis", 36000},
+	{"Angel", 32000},
+	{"Spike", 28000},
+	{"Fox", 24000},
+	{"Raguel", 20000},
+	{"Kumo", 16000},
+	{"Patty", 12000},
+	{"Yuurei", 8000},
+	{"Glurp", 4000}  
+};
+
+HighScore defaultScores[10] = 
+{	
+	{"Leviathan", 40000},
+	{"Dr. Crisis", 36000},
+	{"Angel", 32000},
+	{"Spike", 28000},
+	{"Fox", 24000},
+	{"Raguel", 20000},
+	{"Kumo", 16000},
+	{"Patty", 12000},
+	{"Yuurei", 8000},
+	{"Glurp", 4000}  
+};
+
+char highScoreName[256];
+char *highScoreText, *highScoreRank;
+
+#define min(x,y) (((x)<(y))?(x):(y))
+
+static void FadeScreen( SDL_Surface* hiScoreSurface, SDL_Surface* fadeSurface, int start, int end )
+{
+	int       skip, timer, frame, fade, color, direction, fadeStart, fadeEnd;
+	SDL_Rect  destSDLRect;
+	SDL_Rect  fullSDLRect = { 0, 0, 640, 480 };
+	int       black;
+	
+	black = SDL_MapRGB( fadeSurface->format, 0, 0, 0 );
+	
+	if( start < end )
+	{
+		direction = 1;
+		fadeStart = 0;
+		fadeEnd = 32;
+	}
+	else
+	{
+		direction = -1;
+		fadeStart = 32;
+		fadeEnd = 0;
+	}
+	
+	skip = 1;
+	timer = MTickCount( ) + 1;
+	while( timer > MTickCount() ) { }
+
+	for( frame = start; (direction>0)? (frame <= end): (frame >= end); frame += direction )
+	{
+		MRect drawRect = {0, 0, 15, 640};
+		timer += skip;
+		
+		for( fade = fadeStart; fade != fadeEnd; fade += direction )
+		{
+			color = frame + fade;
+			if( color <  0 ) color = 0;
+			if( color > 31 ) color = 31;
+			
+			SDLU_MRectToSDLRect( &drawRect, &destSDLRect );
+
+			switch( color )
+			{
+				case 0:
+					SDLU_BlitSurface( hiScoreSurface, &destSDLRect,
+					                  fadeSurface,    &destSDLRect  ); 
+					break;
+			
+				case 31:
+					SDL_FillRect( fadeSurface, &destSDLRect, black );
+					break;
+				
+				default:
+					SurfaceBlitColorOver(  hiScoreSurface,  fadeSurface, 
+					                      &drawRect,       &drawRect,
+					                       0, 0, 0, color );
+					break;
+			}
+			
+			OffsetMRect( &drawRect, 0, 15 );
+		}
+
+		SDLU_BlitFrontSurface( fadeSurface, &fullSDLRect, &fullSDLRect );
+				
+		if( timer <= MTickCount( ) )
+		{
+			skip = 2;
+		}
+		else
+		{
+			skip = 1;
+			while( timer > MTickCount( ) )
+			{
+				SDLU_Yield();
+			}
+		}
+	}
+}
+
+void ShowHiscore( void )
+{
+	short            count, length;
+	char             myString[256];
+	int              stringLength;
+	SDL_Surface*     hiScoreSurface;
+	SDL_Surface*     fadeSurface;
+	SDL_Rect         fullSDLRect = { 0, 0, 640, 480 };
+	SkittlesFontPtr  font;
+	SDL_Color        anyColor;
+	MPoint            dPoint;
+	const char*      highScores = "HIGH SCORES";
+	int              r, g, b;
+	
+	if( /* the delete key is held down */ 0 ) // <-- MUST BE OBSOLETED ... ACTUALLY MUST BE FIXED, BUT I ALWAYS SEARCH FOR OBSOLETE :)
+	{
+		// If the user holds delete while opening the high scores,
+		// clear the high score table.
+		
+		memcpy( &scores, &defaultScores, sizeof( scores ) );
+		memcpy( &best,   &defaultBest,   sizeof( best   ) );
+	}
+	
+	hiScoreSurface = LoadPICTAsSurface( picBackdrop + (100 * RandomBefore(IsRegistered()? kLevels: kSharewareLevels)), 16 );
+	fadeSurface    = SDLU_InitSurface( &fullSDLRect, 16 );
+
+	font = GetFont( picHiScoreFont );
+
+	SDLU_AcquireSurface( hiScoreSurface );
+		
+	SDLU_GetPixel( hiScoreSurface, RandomBefore( fullSDLRect.w ), RandomBefore( fullSDLRect.h ), &anyColor );
+
+	anyColor.r = anyColor.r * 32 / 256;
+	anyColor.g = anyColor.g * 32 / 256;
+	anyColor.b = anyColor.b * 32 / 256;
+	anyColor.r = min( 31, anyColor.r + 14 );
+	anyColor.g = min( 31, anyColor.g + 14 );
+	anyColor.b = min( 31, anyColor.b + 14 );
+
+	dPoint.v = 20;
+	dPoint.h = 225;
+	for( count=0; highScores[count]; count++ )
+	{
+		SurfaceBlitCharacter( font, highScores[count], &dPoint, 31, 31, 31, 1 );			
+	}
+	
+	for( count=0; count<=9; count++ )
+	{
+		r = ((31 * (10-count)) + (anyColor.r * count)) / 10;
+		g = ((31 * (10-count)) + (anyColor.g * count)) / 10;
+		b = ((31 * (10-count)) + (anyColor.b * count)) / 10;
+		
+		dPoint.v = 75 + count * 38;
+		dPoint.h = 85;
+		
+		if( count<9 )
+		{
+			SurfaceBlitCharacter( font, count + '1', &dPoint, r, g, b, 1 );			
+		}
+		else
+		{
+			SurfaceBlitCharacter( font, '1', &dPoint, r, g, b, 1 );			
+			SurfaceBlitCharacter( font, '0', &dPoint, r, g, b, 1 );			
+		}
+		
+		SurfaceBlitCharacter( font, '.', &dPoint, r, g, b, 1 );			
+		SurfaceBlitCharacter( font, ' ', &dPoint, r, g, b, 1 );			
+
+		length = 0;
+		while( scores[count].name[length] && dPoint.h < 430 )
+		{
+			SurfaceBlitCharacter( font, scores[count].name[length++], &dPoint, r, g, b, 1 );			
+		}
+		
+		
+		while( dPoint.h < 450 )
+		{
+			SurfaceBlitCharacter( font, '.', &dPoint, r, g, b, 1 );			
+		}
+		
+		dPoint.h = 470;
+
+		stringLength = sprintf( myString, "%d", scores[count].score );
+		for( length=0; length < stringLength; length++ )
+		{
+			SurfaceBlitCharacter( font, myString[length], &dPoint, r, g, b, 1 );			
+		}
+	}
+	
+	SDLU_ReleaseSurface( hiScoreSurface );
+	
+	SDL_FillRect( frontSurface, &frontSurface->clip_rect, SDL_MapRGB( frontSurface->format, 0, 0, 0 ));
+	SDL_Flip( frontSurface );
+	
+	SDLU_SetBrightness( 1.0f );	
+
+	FadeScreen( hiScoreSurface, fadeSurface, 31, -32 );
+	do
+	{
+	}
+	while( !AnyKeyIsPressed( ) && !SDLU_Button() );
+	FadeScreen( hiScoreSurface, fadeSurface, -31, 32 );
+	
+	SDLU_SetBrightness( 0.0f );	
+
+	SDL_FreeSurface( hiScoreSurface );
+	SDL_FreeSurface( fadeSurface );
+}
+
+void InitPotentialCombos( void )
+{
+	memset( &potentialCombo[0], 0, sizeof(Combo) );
+	memset( &potentialCombo[1], 0, sizeof(Combo) );
+	potentialCombo[0].player = 0;
+	potentialCombo[1].player = 1;
+}
+
+void SubmitCombo( Combo *in )
+{
+	if( in->value > best.value && in->value > evenBetter.value )
+	{
+		PlayMono( kContinueSnd );
+		memcpy( &evenBetter, in, sizeof( Combo ) );
+	}	
+}
+
+// Please note: This function may well be the biggest kludge in Candy Crisis.
+// It relies on tons of insider knowledge. But it works.
+void ShowBestCombo( void )
+{
+	SkittlesFontPtr font;
+	char *bestCombo = "BEST COMBO", bestInfo[256], *scan;
+	MPoint dPoint;
+	int levelCap;
+	
+	font = GetFont( picHiScoreFont );
+	
+	StopBalloon( );
+	InitGame( kAutoControl, kNobodyControl );
+	scoreWindowVisible[0] = false;
+	grayMonitorVisible[0] = false;
+
+	level = best.lv;
+	levelCap = IsRegistered()? kLevels: kSharewareSolitaireLevels;
+	if( (level < 1 || level > levelCap) && level != kTutorialLevel ) 
+	{
+		memcpy( &best, &defaultBest, sizeof(best) );
+		showStartMenu = true;
+		return;
+	}
+
+	BeginRound( false );
+	character[0].dropSpeed = 12;
+	
+	SDLU_AcquireSurface( backdropSurface );
+	
+	dPoint.v = 40;
+	dPoint.h = 225;
+	for( scan = bestCombo; *scan; scan++ )
+	{
+		SurfaceBlitCharacter( font, *scan, &dPoint, 31, 31, 31, 1 );			
+	}
+		
+	sprintf( bestInfo, "%s (%d points)", best.name, best.value );
+	dPoint.v = 410;
+	dPoint.h = 320 - (GetTextWidth( font, bestInfo ) / 2);
+
+	for( scan = bestInfo; *scan; scan++ )
+	{
+		SurfaceBlitCharacter( font, *scan, &dPoint, 31, 31, 31, 1 );			
+	}
+
+	SDLU_ReleaseSurface( backdropSurface );
+	
+	memcpy( grid[0], best.grid, kGridAcross * kGridDown );
+	ResolveSuction( 0 );
+	RedrawBoardContents( 0 );
+	RefreshAll( );
+
+	nextA[0] = best.a;
+	nextB[0] = best.b;
+	nextG[0] = best.g;
+	nextM[0] = best.m;
+	RetrieveBlobs( 0 );
+
+	EraseSpriteBlobs( 0 );
+	blobX[0] = best.x;
+	blobR[0] = best.r;
+	DrawSpriteBlobs( 0, kNoSuction );
+	
+	QuickFadeIn( NULL );
+	blobTime[0] = animTime[0] = GameTickCount( );
+
+	autoPattern = hiScorePattern;	
+	tutorialTime = 0;
+}
+
+void AddHiscore( long score )
+{
+	short count, item;
+	char rank[50];
+	char text[256], *playerName = "You", *twoPlayerNames[] = { "Player 1", "Player 2" };
+	int highScoreLevel = -1;
+	
+
+	// Check for high score
+	// (only do high scores if it's player vs CPU)
+			
+	if( players == 1 &&
+	    control[0] == kPlayerControl &&
+	    control[1] == kAIControl        )
+	{		
+		for( count=0; count<=9; count++ )
+		{
+			if( score >= scores[count].score )
+			{				
+				sprintf( rank, "%d points", score );
+				highScoreLevel = count;
+				break;
+			}
+		}
+	}
+	
+	// Determine player's name for best combo
+
+	if( players == 2 ) playerName = twoPlayerNames[evenBetter.player];
+
+
+	// See if both best combo AND high score
+		
+	if( evenBetter.value > best.value && highScoreLevel != -1 )
+	{
+		
+		sprintf( text, "You got a high score and the best combo!" );
+
+		highScoreText = text;
+		highScoreRank = rank;
+
+		HandleDialog( kHiScoreDialog );
+
+		if( !finished )
+		{
+			highScoreName[kNameLength] = '\0';
+
+			memcpy( &best, &evenBetter, sizeof(Combo) );
+			strcpy( best.name, highScoreName );
+
+			for( item=8; item>=highScoreLevel; item-- )
+			{
+				memmove( &scores[item+1], &scores[item], sizeof( HighScore ) );
+			}
+			
+			scores[highScoreLevel].score = score;
+			strcpy( scores[highScoreLevel].name, highScoreName );				
+		}
+	}
+	
+	// See if best combo has been won
+		
+	else if( evenBetter.value > best.value )
+	{
+		sprintf( text, "Congratulations! %s got best combo!", playerName );
+		
+		highScoreText = text;
+		highScoreRank = "";
+				
+		HandleDialog( kHiScoreDialog );
+		
+		if( !finished )
+		{
+			highScoreName[kNameLength] = '\0';
+
+			memcpy( &best, &evenBetter, sizeof(Combo) );
+			strcpy( best.name, highScoreName );
+		}
+	}
+
+	// See if high score has been won
+	
+	else if( highScoreLevel != -1 )
+	{
+		highScoreText = "Congratulations! You got a high score!";
+		highScoreRank = rank;
+
+		HandleDialog( kHiScoreDialog );
+		
+		if( !finished )
+		{
+			highScoreName[kNameLength] = '\0';
+
+			for( item=8; item>=highScoreLevel; item-- )
+			{
+				memmove( &scores[item+1], &scores[item], sizeof( HighScore ) );
+			}
+			
+			scores[highScoreLevel].score = score;
+			strcpy( scores[highScoreLevel].name, highScoreName );				
+		}
+	}
+}
--- /dev/null
+++ b/Source/hiscore.h
@@ -1,0 +1,28 @@
+// hiscore.h
+
+#define kNameLength 40
+
+typedef struct
+{
+	signed char grid[kGridAcross][kGridDown];
+	signed char a, b, m, g, lv, x, r, player;
+	int value;
+	char name[kNameLength];
+} Combo;
+
+typedef struct 
+{
+	char name[kNameLength];
+	long score;
+} HighScore;
+
+void ShowHiscore( void );
+void ShowBestCombo( void );
+void AddHiscore( long score );
+void SubmitCombo( Combo *in );
+void InitPotentialCombos( void );
+
+extern Combo best, potentialCombo[2];
+extern HighScore scores[10];
+extern char highScoreName[256];
+extern char *highScoreText, *highScoreRank;
--- /dev/null
+++ b/Source/keyselect.cpp
@@ -1,0 +1,64 @@
+// keyselect.c
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "main.h"
+#include "players.h"
+#include "keyselect.h"
+
+
+SDLKey playerKeys[2][4] = {
+			{ SDLK_a, SDLK_d, SDLK_x, SDLK_s },
+			{ SDLK_LEFT, SDLK_RIGHT, SDLK_DOWN, SDLK_UP }
+		                  };
+
+const SDLKey defaultPlayerKeys[2][4] = {
+			{ SDLK_a, SDLK_d, SDLK_x, SDLK_s },
+			{ SDLK_LEFT, SDLK_RIGHT, SDLK_DOWN, SDLK_UP }
+		                  };
+
+
+void CheckKeys()
+{
+	int player;
+	int arraySize;
+	unsigned char* pressedKeys;
+				                 
+	SDLU_PumpEvents();          
+	pressedKeys = SDL_GetKeyState( &arraySize );
+		
+	// Check for game keys
+	for( player = 0; player < 2; player++ )
+	{
+		if( pressedKeys[ playerKeys[player][0] ] )
+			hitKey[player].left++; 
+		else
+			hitKey[player].left = 0;
+
+
+		if( pressedKeys[ playerKeys[player][1] ] )		
+			hitKey[player].right++; 
+		else
+			hitKey[player].right = 0;
+
+
+		if( pressedKeys[ playerKeys[player][2] ] ) 			
+			hitKey[player].drop++; 
+		else
+			hitKey[player].drop = 0;
+
+
+		if( pressedKeys[ playerKeys[player][3] ] )
+			hitKey[player].rotate++; 
+		else
+			hitKey[player].rotate = 0;
+	}
+	
+	pauseKey = pressedKeys[ SDLK_ESCAPE ];
+}
--- /dev/null
+++ b/Source/keyselect.h
@@ -1,0 +1,16 @@
+// keyselect.h
+
+
+#ifdef __cplusplus
+extern "C"
+#endif
+int SDLTypingFilter(const SDL_Event *event);
+
+void StartWatchingTyping();
+void StopWatchingTyping();
+MBoolean CheckTyping( char* ascii, SDLKey* sdl );
+void CheckKeys();
+
+
+extern SDLKey playerKeys[2][4];
+extern const SDLKey defaultPlayerKeys[2][4];
--- /dev/null
+++ b/Source/level.cpp
@@ -1,0 +1,1338 @@
+// level.c
+
+#include <stdlib.h>
+#include <math.h>
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include "main.h"
+#include "level.h"
+#include "score.h"
+#include "random.h"
+#include "grays.h"
+#include "gameticks.h"
+#include "players.h"
+#include "graymonitor.h"
+#include "opponent.h"
+#include "gworld.h"
+#include "graphics.h"
+#include "music.h"
+#include "control.h"
+#include "tweak.h"
+#include "soundfx.h"
+#include "next.h"
+#include "hiscore.h"
+#include "victory.h"
+#include "blitter.h"
+#include "zap.h"
+#include "keyselect.h"
+#include "tutorial.h"
+#include "pause.h"
+
+MRect stageWindowZRect, stageWindowRect;
+Character character[2];
+int level, players, credits, difficulty[2] = {kHardLevel, kHardLevel};
+int difficultyTicks, backdropTicks, backdropFrame;
+
+#define kNumSplats 16
+#define kIdleSplat -2
+#define kFallingSplat -1
+#define kTitleItems 7
+#define kIncrementPerFrame 2
+#define kSplatType 4
+
+#define min(x,y) (((x)<(y))?(x):(y))
+#define max(x,y) (((x)>(y))?(x):(y))
+
+
+const int startSkip = 1;
+static MBoolean shouldFullRepaint = false;
+static int startMenuTime = 0;
+static int splatState[kNumSplats], splatColor[kNumSplats], splatSide[kNumSplats];
+static MRect splatBlob[kNumSplats];
+static int glowUpdate = 0, titleGlow[kTitleItems] = {24, 24, 24, 24, 24, 24, 24};
+static MRect titleRect[kTitleItems] = {
+		{ 155, 203, 207, 426 }, // tutorial
+		{ 225, 179, 281, 451 }, // 1p
+		{ 297, 182, 352, 454 }, // 2p
+		{ 358, 183, 428, 458 }, // solitaire
+		{ 429, 280, 478, 390 }, // high scores
+		{ 433, 390, 477, 446 }, // quit
+		{ 430, 187, 479, 280 }, // controls
+		                };
+
+const int kCursorWidth  = 32;
+const int kCursorHeight = 32;
+
+static void InsertCursor( MPoint mouseHere, SDL_Surface* scratch, SDL_Surface* surface )
+{
+	SkittlesFontPtr cursorFont = GetFont( picFont );
+	SDL_Rect        cursorBackSDLRect = { 0, 0, kCursorWidth, kCursorHeight };
+	SDL_Rect        cursorFrontSDLRect = { 0, 0, kCursorWidth, kCursorHeight };
+	MPoint          mouseHereToo = mouseHere;
+	
+	cursorFrontSDLRect.x = mouseHere.h;
+	cursorFrontSDLRect.y = mouseHere.v;
+	
+	SDLU_BlitSurface( surface, &cursorFrontSDLRect,
+	                  scratch, &cursorBackSDLRect   );
+	
+	SDLU_AcquireSurface( surface );
+	SurfaceBlitCharacter( cursorFont, '�', &mouseHere,    0,  0,  0, 0 );			
+	SurfaceBlitCharacter( cursorFont, '�', &mouseHereToo, 31, 31, 31, 0 );			
+	SDLU_ReleaseSurface( surface );
+}
+
+static void RemoveCursor( MPoint mouseHere, SDL_Surface* scratch, SDL_Surface* surface )
+{
+	SDL_Rect      cursorBackSDLRect = { 0, 0, kCursorWidth, kCursorHeight };
+	SDL_Rect      cursorFrontSDLRect = { 0, 0, kCursorWidth, kCursorHeight };
+	MPoint        mouseHereToo = mouseHere;
+	
+	cursorFrontSDLRect.x = mouseHere.h;
+	cursorFrontSDLRect.y = mouseHere.v;
+	
+	SDLU_BlitSurface( scratch, &cursorBackSDLRect,
+	                  surface, &cursorFrontSDLRect );
+}
+
+static void GameStartMenuRepaint()
+{
+	shouldFullRepaint = true;
+}
+
+void GameStartMenu( void )
+{
+	// NOTE: be wary of initializing variables here! This function can run top-to-bottom
+	// multiple times in a row, thanks to "redo". Put initializations after redo.
+    SDL_Surface*    gameStartSurface;
+    SDL_Surface*    gameStartDrawSurface;
+    SDL_Surface*    cursorBackSurface;
+	MRect           backdropPortZRect = { 0, 0, 480, 640 };
+	SDL_Rect        backdropSDLRect = { 0, 0, 640, 480 };
+	SDL_Rect        cursorBackSDLRect = { 0, 0, kCursorWidth, kCursorHeight };
+	SDL_Rect        cursorFrontSDLRect = { 0, 0, kCursorWidth, kCursorHeight };
+	SDL_Rect        destSDLRect;
+	MRect           drawRect[4], chunkRect, tempRect;
+	int             blob, count, oldGlow, splat, chunkType, selected;
+	int             skip;
+	MPoint          mouse;
+	MPoint          dPoint;
+	MPoint          sPoint[4];
+	unsigned long   black;
+	int             currentID;
+	char            registeredString[256];
+	char*           scan;
+	int             combo[2], comboBright[2], missBright[2];
+	SkittlesFontPtr smallFont = GetFont( picFont );
+	SkittlesFontPtr tinyFont = GetFont( picTinyFont );
+	SDL_Rect        meterRect[2] = { { 30, 360, 110, 20 }, { 530, 360, 110, 20 } };
+ 
+	const int       kLeftSide = 0, kRightSide = 1, kGlow = 2, kCursor = 3;
+	
+redo:
+
+	combo[0] = combo[1] = 0;
+	comboBright[0] = comboBright[1] = 0;
+	missBright[0] = missBright[1] = 0;
+		
+	skip = 1;
+	selected = -1;
+	mouse.h = mouse.v = 0;
+	
+	if( finished ) return;
+	
+	if( musicSelection != 13 ) ChooseMusic( 13 );
+	
+	for( count=0; count<kTitleItems; count++ )
+	{
+		titleGlow[count] = 24;
+	}
+	
+	for( count=0; count<kNumSplats; count++ )
+	{
+		splatState[count] = kIdleSplat;
+	}
+	
+	// make background surface
+	gameStartSurface     = LoadPICTAsSurface( picGameStart, 16 );
+	black = SDL_MapRGB( gameStartSurface->format, 0, 0, 0 );
+
+	// make cursor backing store
+	cursorBackSurface    = SDLU_InitSurface( &cursorBackSDLRect, 16 );
+	SDL_FillRect( cursorBackSurface, &cursorBackSDLRect, black );
+	
+	// draw user name with a cool blue halo
+	if( IsRegistered() )
+	{
+		sprintf( registeredString, "Registered to %s", registeredName );
+	}
+	else
+	{
+		sprintf( registeredString, "U N R E G I S T E R E D" );
+	}
+	
+	dPoint.v = 131;
+	dPoint.h = 320 - (GetTextWidth( smallFont, registeredString ) / 2);
+	sPoint[0].v = dPoint.v + 1;	sPoint[0].h = dPoint.h + 1;
+	sPoint[1].v = dPoint.v - 1;	sPoint[1].h = dPoint.h - 1;
+	sPoint[2].v = dPoint.v - 1;	sPoint[2].h = dPoint.h + 1;
+	sPoint[3].v = dPoint.v + 1;	sPoint[3].h = dPoint.h - 1;
+ 
+	SDLU_AcquireSurface( gameStartSurface );
+	for( scan = registeredString; *scan; scan++ )
+	{		
+		SurfaceBlitCharacter( smallFont, *scan, &sPoint[0], 0, 0, 25, 0 );			
+		SurfaceBlitCharacter( smallFont, *scan, &sPoint[1], 0, 0, 25, 0 );			
+		SurfaceBlitCharacter( smallFont, *scan, &sPoint[2], 0, 0, 25, 0 );			
+		SurfaceBlitCharacter( smallFont, *scan, &sPoint[3], 0, 0, 25, 0 );			
+		SurfaceBlitCharacter( smallFont, *scan, &dPoint, 30, 30, 30, 0 );			
+	}
+	SDLU_ReleaseSurface( gameStartSurface );
+	
+	// make drawing surface
+	gameStartDrawSurface = SDLU_InitSurface( &backdropSDLRect, 16 );
+	SDLU_BlitSurface( gameStartSurface,     &gameStartSurface->clip_rect,
+					  gameStartDrawSurface, &gameStartDrawSurface->clip_rect );
+	
+	// darken menu items
+	for( count=0; count<kTitleItems; count++ )
+	{
+		SurfaceBlitColorOver(  gameStartSurface,  gameStartDrawSurface, 
+		                      &titleRect[count], &titleRect[count], 
+		                       0, 0, 0, titleGlow[count] );
+	}
+	
+	SDLU_BlitFrontSurface( gameStartDrawSurface, &backdropSDLRect, &backdropSDLRect );
+
+	WaitForRelease();
+
+	QuickFadeIn( NULL );
+	
+	DoFullRepaint = GameStartMenuRepaint;
+
+	startMenuTime = MTickCount( );
+	while( ( selected == -1 || !SDLU_Button() ) && !finished )
+	{	
+		startMenuTime += skip;
+
+		// Add a new falling blob 
+		if( glowUpdate == 0 ) 
+		{
+			for( blob=0; blob<kNumSplats; blob++ )
+			{
+				if( splatState[blob] == kIdleSplat )
+				{
+					splatSide[blob] = RandomBefore(2);
+					splatBlob[blob].top = -24 - RandomBefore(15);
+					splatBlob[blob].left = (splatSide[blob] == 0)? RandomBefore( 110 ): 640 - kBlobHorizSize - RandomBefore( 110 );
+					splatBlob[blob].bottom = splatBlob[blob].top + kBlobVertSize;
+					splatBlob[blob].right = splatBlob[blob].left + kBlobHorizSize;
+					splatColor[blob] = ((startMenuTime >> 2) % kBlobTypes) + 1;
+					splatState[blob] = kFallingSplat;
+					
+					break;
+				}
+			}
+		}
+	
+		// Erase and redraw falling blobs and chunks
+
+		SDLU_AcquireSurface( gameStartDrawSurface );
+	
+		// Take the cursor out of the scene
+		RemoveCursor( mouse, cursorBackSurface, gameStartDrawSurface );
+		drawRect[kCursor].top    = mouse.v;
+		drawRect[kCursor].left   = mouse.h;
+		drawRect[kCursor].bottom = mouse.v + kCursorHeight;
+		drawRect[kCursor].right  = mouse.h + kCursorWidth;
+		
+		// is this a hack? maybe. but it works!
+		drawRect[kLeftSide].top    = drawRect[kRightSide].top    = drawRect[kGlow].top    = 
+		drawRect[kLeftSide].left   = drawRect[kRightSide].left   = drawRect[kGlow].left   = 9999;
+		drawRect[kLeftSide].bottom = drawRect[kRightSide].bottom = drawRect[kGlow].bottom =
+		drawRect[kLeftSide].right  = drawRect[kRightSide].right  = drawRect[kGlow].right  = -9999;
+	
+		// Get cursor position		
+		SDLU_GetMouse( &mouse );
+        if( mouse.v > 460 ) mouse.v = 460;
+        
+		// Erase falling blobs
+		for( blob=0; blob<kNumSplats; blob++ )
+		{
+			if( splatState[blob] == kFallingSplat )
+			{
+				SDL_FillRect( gameStartDrawSurface, SDLU_MRectToSDLRect( &splatBlob[blob], &destSDLRect ), black );
+				UnionMRect( &drawRect[splatSide[blob]], &splatBlob[blob], &drawRect[splatSide[blob]] );
+				
+				OffsetMRect( &splatBlob[blob], 0, startSkip * (6 + (splatBlob[blob].bottom / 20)) );
+			}
+			else if( splatState[blob] >= kIncrementPerFrame )
+			{
+				for( splat=-3; splat<=3; splat++ )
+				{
+					if( splat )
+					{
+						chunkRect = splatBlob[blob];
+						GetZapStyle( 0, &chunkRect, &splatColor[blob], &chunkType, splat, splatState[blob]-kIncrementPerFrame, kSplatType );
+						SDL_FillRect( gameStartDrawSurface, SDLU_MRectToSDLRect( &chunkRect, &destSDLRect ), black );
+						UnionMRect( &drawRect[splatSide[blob]], &chunkRect, &drawRect[splatSide[blob]] );
+					}
+				}
+				
+				SDL_FillRect( gameStartDrawSurface, SDLU_MRectToSDLRect( &splatBlob[blob], &destSDLRect ), black );
+				UnionMRect( &drawRect[splatSide[blob]], &splatBlob[blob], &drawRect[splatSide[blob]] );
+			}
+		}
+	
+		// Draw combo meters (GEEK!!!!!)
+        
+		for( count=0; count<2; count++ )
+		{
+			int bright = comboBright[count];
+			int mBright = missBright[count];
+            if( bright || mBright )
+			{
+                SDL_FillRect( gameStartDrawSurface, &meterRect[count], black );
+                UnionMRect( &drawRect[count], SDLU_SDLRectToMRect( &meterRect[count], &tempRect ), &drawRect[count] );
+                
+                if( mBright > 1 )
+                {
+                  dPoint.v = meterRect[count].y;
+                  dPoint.h = meterRect[count].x + 10;
+                  SurfaceBlitCharacter( smallFont, 'M', &dPoint, mBright, mBright >> 2, mBright >> 2, 1 );
+                  SurfaceBlitCharacter( smallFont, 'I', &dPoint, mBright, mBright >> 2, mBright >> 2, 1 );
+                  SurfaceBlitCharacter( smallFont, 'S', &dPoint, mBright, mBright >> 2, mBright >> 2, 1 );
+                  SurfaceBlitCharacter( smallFont, 'S', &dPoint, mBright, mBright >> 2, mBright >> 2, 1 );
+                  missBright[count]--;
+                }
+                else if( (combo[count] >= 10) && (bright > 1) )
+                {
+				  char  number[16] = { 0 };
+				  char* scan;
+				  sprintf( number, "%d", combo[count] );
+
+                  dPoint.v = meterRect[count].y + 3;
+                  dPoint.h = meterRect[count].x;
+     
+				  SurfaceBlitCharacter( tinyFont, 'C', &dPoint, bright, bright, bright, 1 );
+				  SurfaceBlitCharacter( tinyFont, 'O', &dPoint, bright, bright, bright, 1 );
+				  SurfaceBlitCharacter( tinyFont, 'M', &dPoint, bright, bright, bright, 1 );
+				  SurfaceBlitCharacter( tinyFont, 'B', &dPoint, bright, bright, bright, 1 );
+				  SurfaceBlitCharacter( tinyFont, 'O', &dPoint, bright, bright, bright, 1 );
+				  SurfaceBlitCharacter( tinyFont, ' ', &dPoint, bright, bright, bright, 1 );
+				  dPoint.v -= 3;
+				
+				  for( scan = number; *scan; scan++ )
+				  {
+				 	SurfaceBlitCharacter( smallFont, *scan, &dPoint, bright>>2, bright>>2, bright, 1 );
+			      }
+                  
+                  comboBright[count] -= 2;
+				}
+                else 
+                {
+                  comboBright[count] = 0;
+                }
+   			}
+		}
+
+		// Redraw falling blobs
+		for( blob=0; blob<kNumSplats; blob++ )
+		{
+			if( splatState[blob] == kFallingSplat )
+			{
+				if( splatBlob[blob].bottom >= 480 ) 
+				{
+					splatBlob[blob].top = 480 - kBlobVertSize;
+					splatBlob[blob].bottom = 480;
+					splatState[blob] = 1;
+					
+                    // Process combos
+					if( mouse.v > 420 &&
+					    mouse.h >= (splatBlob[blob].left - 30) &&
+					    mouse.h <= (splatBlob[blob].right + 10)    )
+					{
+						combo[splatSide[blob]]++;
+						comboBright[splatSide[blob]] = 31;
+					}
+					else
+					{
+                        if( combo[splatSide[blob]] >= 10 ) missBright[splatSide[blob]] = 31;
+						combo[splatSide[blob]] = 0;
+                        comboBright[splatSide[blob]] = 0;                        
+					}
+				}
+				else
+				{
+					SurfaceDrawSprite( &splatBlob[blob], splatColor[blob], kNoSuction );
+					UnionMRect( &drawRect[splatSide[blob]], &splatBlob[blob], &drawRect[splatSide[blob]] );
+				}
+			}
+			
+			if( splatState[blob] >= 0 && splatState[blob] <= kZapFrames )
+			{
+				if( splatState[blob] <= (kZapFrames - kIncrementPerFrame) ) 
+				{
+					for( splat=-3; splat<=3; splat++ )
+					{
+						if( splat )
+						{
+							chunkRect = splatBlob[blob];
+							GetZapStyle( 0, &chunkRect, &splatColor[blob], &chunkType, splat, splatState[blob], kSplatType );
+							SurfaceDrawSprite( &chunkRect, splatColor[blob], chunkType );
+							UnionMRect( &drawRect[splatSide[blob]], &chunkRect, &drawRect[splatSide[blob]] );
+						}
+					}
+					
+					SurfaceDrawSprite( &splatBlob[blob], splatColor[blob], chunkType );
+					UnionMRect( &drawRect[splatSide[blob]], &splatBlob[blob], &drawRect[splatSide[blob]] );
+				}
+				
+				splatState[blob] += kIncrementPerFrame;
+				if( splatState[blob] > kZapFrames ) splatState[blob] = kIdleSplat;
+			}
+		}
+				
+		SDLU_ReleaseSurface( gameStartDrawSurface );		
+		
+		// Find mouse coords
+		
+		selected = -1;			
+		for( count=0; count<kTitleItems; count++ )
+		{
+			if( MPointInMRect( mouse, &titleRect[count] ) )
+			{
+				selected = count;
+				break;
+			}
+		}
+
+		// update glows
+		do
+		{
+            glowUpdate = (glowUpdate + 1) % 7;
+
+			oldGlow = titleGlow[glowUpdate];
+			
+			if( selected == glowUpdate )
+			{
+				titleGlow[glowUpdate] -= (4 * startSkip);
+				if( titleGlow[glowUpdate] < 0 ) titleGlow[glowUpdate] = 0;
+			}
+			else 
+			{
+				titleGlow[glowUpdate] += (4 * startSkip);
+				if( titleGlow[glowUpdate] > 24 ) titleGlow[glowUpdate] = 24;
+			}
+			
+			if( titleGlow[glowUpdate] != oldGlow )
+			{
+				SurfaceBlitColorOver(  gameStartSurface,       gameStartDrawSurface,
+				                      &titleRect[glowUpdate], &titleRect[glowUpdate],
+				                       0, 0, 0, titleGlow[glowUpdate] );
+
+				drawRect[kGlow] = titleRect[glowUpdate];
+			}
+
+			// do 5, 6, and 7 all at once because they're small
+		    if( glowUpdate == 4 || glowUpdate == 5 ) continue;
+		}
+		while( 0 );
+
+		// Reinsert the cursor into the scene
+		InsertCursor( mouse, cursorBackSurface, gameStartDrawSurface );
+		drawRect[kCursor].top    = min( drawRect[kCursor].top,    mouse.v );
+		drawRect[kCursor].left   = min( drawRect[kCursor].left,   mouse.h );
+		drawRect[kCursor].bottom = max( drawRect[kCursor].bottom, mouse.v + kCursorHeight );
+		drawRect[kCursor].right  = max( drawRect[kCursor].right,  mouse.h + kCursorWidth );
+
+		// Copy down everything		
+		if( shouldFullRepaint )
+		{
+			SDLU_BlitFrontSurface( gameStartDrawSurface, &gameStartDrawSurface->clip_rect, &gameStartDrawSurface->clip_rect );
+			shouldFullRepaint = false;
+		}
+		else
+		{
+			for( count=0; count<4; count++ )
+			{		
+				if( drawRect[count].left < drawRect[count].right )
+				{
+					SDLU_MRectToSDLRect( &drawRect[count], &destSDLRect );			
+					SDLU_BlitFrontSurface( gameStartDrawSurface, &destSDLRect, &destSDLRect );
+				}
+			}
+		}
+				
+		// Skip frames? Or delay?
+		if( startMenuTime <= MTickCount( ) )
+		{
+			startMenuTime = MTickCount( );
+			skip = 2;
+		}
+		else
+		{
+			skip = 1;
+			while( startMenuTime > MTickCount( ) ) 
+			{   
+                SDLU_Yield();
+			}
+		}
+	}
+
+	DoFullRepaint = NoPaint;
+
+	if( finished ) 
+	{
+		selected = 5; // quit
+	}
+	
+	switch( selected )
+	{
+		case 0: 
+		case 1: 
+		case 2:
+		case 3:
+			PlayMono( kChime ); 
+			break;
+	}
+
+	SDL_FreeSurface( gameStartSurface );
+	SDL_FreeSurface( gameStartDrawSurface );
+	SDL_FreeSurface( cursorBackSurface );
+	
+	QuickFadeOut( NULL );
+
+	switch( selected )
+	{
+		case 0: 
+			InitGame( kAutoControl, kNobodyControl );
+			level = kTutorialLevel;
+			BeginRound( true );
+			InitTutorial( );
+			QuickFadeIn( NULL );
+			break;				
+
+		case 1:
+		case 2:
+		case 3:
+			{
+				int player2[] = { 0, kAIControl, kPlayerControl, kNobodyControl };
+				
+				InitGame( kPlayerControl, player2[selected] );
+				BeginRound( true );
+				QuickFadeIn( NULL );
+				break;
+			}
+		
+		case 4: 
+			ShowHiscore();
+			ShowBestCombo();
+			break;
+			
+		case 5:
+			finished = true;
+			break;
+		
+		case 6:
+			currentID = RandomBefore( IsRegistered()? kLevels: kSharewareLevels ) * 100;
+	
+			DrawPICTInSurface( boardSurface[0], picBoard + currentID );	
+			DrawPICTInSurface( frontSurface, picBackdrop + currentID );
+			SDL_Flip( frontSurface );		
+	
+			QuickFadeIn( NULL );
+			HandleDialog( kControlsDialog );
+			QuickFadeOut( NULL );
+			goto redo;
+	}
+}
+
+void ShowGameOverScreen( void )
+{
+	unsigned long timer = MTickCount() + (60*3);
+	
+	QuickFadeOut(NULL);
+
+	DrawPICTInSurface( frontSurface, picGameOver );
+	SDL_Flip( frontSurface );
+
+	QuickFadeIn( NULL );
+	do
+	{
+		if( MTickCount() > timer ) break;
+		SDLU_Yield();
+	}
+	while( !AnyKeyIsPressed( ) && !SDLU_Button() );
+	QuickFadeOut( NULL );
+}
+
+void InitStage( void )
+{
+	stageWindowZRect.top = stageWindowZRect.left = 0;
+	stageWindowZRect.bottom = 32; stageWindowZRect.right = 64; 
+	
+	stageWindowRect = stageWindowZRect;
+	CenterRectOnScreen( &stageWindowRect, 0.5, 0.65 );
+}
+
+void DrawStage( void )
+{
+	SDL_Surface* levelSurface;
+	SDL_Rect     sourceSDLRect, destSDLRect;
+	MRect        numberRect = { 0, kNumberHorizSize/8, kNumberVertSize, kNumberHorizSize*9/8 };
+			
+	switch( players )
+	{
+		case 0:
+		case 2:
+			break;
+			
+		case 1:
+			SDLU_MRectToSDLRect( &stageWindowZRect, &sourceSDLRect );
+			SDLU_MRectToSDLRect( &stageWindowRect,  &destSDLRect );
+			
+			levelSurface = SDLU_InitSurface( &sourceSDLRect, 16 );
+
+			SDLU_AcquireSurface( levelSurface );
+			
+			SDLU_BlitSurface( boardSurface[0], &sourceSDLRect,
+							  levelSurface,    &sourceSDLRect   );
+			
+			if( level < 10 )
+			{
+				OffsetMRect( &numberRect, kNumberHorizSize*3/8, 0 );
+			}
+			
+			DrawCharacter( kCharacterStage,   &numberRect );
+			OffsetMRect( &numberRect, kNumberHorizSize, 0 );
+			DrawCharacter( kCharacterStage+1, &numberRect );
+
+			if( level < 10 )
+			{
+				OffsetMRect( &numberRect, kNumberHorizSize, 0 );
+				DrawCharacter( level + '0', &numberRect );
+			}
+			else
+			{
+				OffsetMRect( &numberRect, kNumberHorizSize*3/4, 0 );
+				DrawCharacter( (level / 10) + '0', &numberRect );
+				OffsetMRect( &numberRect, kNumberHorizSize, 0 );
+				DrawCharacter( (level % 10) + '0', &numberRect );
+			}
+
+			SDLU_BlitFrontSurface( levelSurface, &sourceSDLRect, &destSDLRect );
+			
+			SDL_FreeSurface( levelSurface );
+						
+			break;
+	}
+}
+
+void InitGame( int player1, int player2 )
+{
+	playerWindowVisible[0] = true;
+	nextWindowVisible[0] = true;
+	scoreWindowVisible[0] = true;
+	grayMonitorVisible[0] = true;
+	
+	if( player2 == kNobodyControl )
+	{
+		playerWindowVisible[1] = false;
+		nextWindowVisible[1] = false;
+		scoreWindowVisible[1] = false;
+		grayMonitorVisible[1] = false;
+
+		CenterRectOnScreen( &playerWindowRect[0], 0.5, 0.5  );
+		CenterRectOnScreen( &scoreWindowRect[0],  0.5, 0.89 );
+		CenterRectOnScreen( &grayMonitorRect[0],  0.5, 0.11 );
+		CenterRectOnScreen( &nextWindowRect[0],   0.3, 0.25 );
+		
+		CenterRectOnScreen( &stageWindowRect,	  0.3, 0.65 );		
+		CenterRectOnScreen( &opponentWindowRect,  0.3, 0.5 );		
+	}
+	else
+	{
+		playerWindowVisible[1] = true;
+		nextWindowVisible[1] = true;
+		scoreWindowVisible[1] = true;
+		grayMonitorVisible[1] = true;
+
+		CenterRectOnScreen( &playerWindowRect[0], kLeftPlayerWindowCenter, 0.5  );
+		CenterRectOnScreen( &scoreWindowRect[0],  kLeftPlayerWindowCenter, 0.89 );
+		CenterRectOnScreen( &grayMonitorRect[0],  kLeftPlayerWindowCenter, 0.11 );
+		CenterRectOnScreen( &nextWindowRect[0],   0.46, 0.25 );
+
+		CenterRectOnScreen( &playerWindowRect[1], kRightPlayerWindowCenter, 0.5  );
+		CenterRectOnScreen( &scoreWindowRect[1],  kRightPlayerWindowCenter, 0.89 );
+		CenterRectOnScreen( &grayMonitorRect[1],  kRightPlayerWindowCenter, 0.11 );
+		CenterRectOnScreen( &nextWindowRect[1],   0.54, 0.25 );
+
+		CenterRectOnScreen( &stageWindowRect,    0.5, 0.65 );		
+		CenterRectOnScreen( &opponentWindowRect, 0.5, 0.5 );		
+	}
+	
+	nextWindowVisible[0] = ( player1 == kAutoControl )? false: true;
+	
+	players = (player1 == kPlayerControl) + (player2 == kPlayerControl);
+	
+	if( players < 2 )
+	{
+		difficulty[0] = difficulty[1] = kHardLevel;
+	}
+	
+	control[0] = player1;
+	control[1] = player2;
+	
+	score[0] = score[1] = displayedScore[0] = displayedScore[1] = 0;	
+	roundStartScore[0] = roundStartScore[1] = 0;
+	
+	level = 1;
+	credits = (player2 == kNobodyControl)? 1: 5;
+}
+
+MBoolean InitCharacter( int player, int level )
+{
+	const Character characterList[] = {
+		{ -1 }, // no zero'th character
+		{ 0, 3, 1, { 8, 8, 8, 8, 8, 8 }, 13, 9, 0, 25, { 0, 0, 0, 0 }, true },
+		{ 1, 6, 2, { 10, 9, 8, 8, 9, 10 }, 12, 7, 1, 20, { 223, 7, 0, 0 }, true },
+		{ 2, 9, 3, { 7, 7, 7, 11, 7, 7 }, 10, 6, 2, 17, { 0, 0, 0, 0 }, false },
+		{ 3, 12, 4, { 11, 10, 9, 8, 7, 6 }, 8, 5, 3, 13, { 32767, 4, 16912, 4 }, false },
+		{ 4, 15, 0, { 5, 9, 10, 10, 9, 5 }, 7, 4, 4, 10, { 32767, 1, 0, 0 }, false },
+		{ 5, 17, 1, { 4, 7, 11, 11, 6, 3 }, 7, 2, 5, 8, { 14835, 8, 0, 0 }, false },
+		{ 6, 18, 2, { 7, 9, 10, 10, 9, 7 }, 6, 4, 6, 7, { 0, 0, 0, 0 }, false },
+		{ 7, 20, 3, { 5, 10, 10, 10, 10, 5 }, 5, 3, 7, 5, { 9696, 2, 21151, 3 }, false },
+		{ 8, 21, 4, { 11, 11, 10, 10, 9, 9 }, 4, 3, 8, 5, { 32738, 5, 0, 0 }, false },
+		{ 9, 22, 0, { 11, 7, 11, 7, 11, 7 }, 3, 1, 9, 4, { 32356, 5, 17392, 3 }, false },
+		{ 10, 23, 1, { 11, 11, 11, 11, 11, 11 }, 2, 1, 10, 2, { 6337, 1, 0, 0 }, false },
+		{ 11, 24, 2, { 11, 11, 11, 11, 11, 11 }, 2, 1, 11, 2, { -1, 7, 0, 0 }, false },
+		{ -1 }, // skip
+		{ 13, 24, 1, { 11, 11, 11, 11, 11, 11 }, 10, 5, 0, 30, { 0, 0, 0, 0 }, true }
+	};
+		
+	if( !IsRegistered() )
+	{
+		int levelCap = kSharewareLevels;
+		if( control[1] == kNobodyControl ) levelCap = kSharewareSolitaireLevels;
+		
+		if( level > levelCap && level != kTutorialLevel ) return false;
+	}
+
+	character[player] = characterList[level];
+	return (character[player].picture != -1);
+}
+
+void PrepareStageGraphics( int type )
+{
+	int player;
+	                            
+	MRect blobBoard = { 0, 0, kGridDown * kBlobVertSize, kGridAcross * kBlobHorizSize };
+	
+	backgroundID = type * 100;
+	
+	DrawPICTInSurface( boardSurface[0], picBoard + backgroundID );	
+	
+	// NOTE: Many levels have no right-side board, so we copy the left
+	// side over to the right side. This way, if DrawPICTInSurface flunks,
+	// we still have a valid picture.
+	
+	SDLU_BlitSurface( boardSurface[0], &boardSurface[0]->clip_rect,
+	                  boardSurface[1], &boardSurface[1]->clip_rect  );
+	
+	DrawPICTInSurface( boardSurface[1], picBoardRight + backgroundID );	
+
+	DrawPICTInSurface( backdropSurface, picBackdrop + backgroundID );
+
+	DrawPICTInSurface( nextSurface, picNext + backgroundID );
+	
+	for( player=0; player<=1; player++ )
+	{
+		SDLU_AcquireSurface( playerSurface[player] );
+		SurfaceDrawBoard( player, &blobBoard );
+		SDLU_ReleaseSurface( playerSurface[player] );
+		
+		CleanSpriteArea( player, &blobBoard );
+	}
+	
+	BeginOpponent( type );
+
+	RedrawBoardContents( 0 );
+	RedrawBoardContents( 1 );
+	
+	RefreshAll( );
+
+	backdropTicks = MTickCount( );
+	backdropFrame = 0;
+}
+
+void BeginRound( MBoolean changeMusic )
+{
+	int player, count, count2;
+	
+	InitGrays( );
+	InitPotentialCombos( );
+	
+	switch( players )
+	{
+		case 0:
+		case 1:
+			if( InitCharacter( 1, level ) )
+			{
+				score[1] = roundStartScore[1] = displayedScore[1] = 0;
+				character[0] = character[1];
+				character[0].zapStyle = RandomBefore(5);
+			}
+			else
+			{
+				TotalVictory( );
+				return;
+			}
+			
+			if( control[1] == kNobodyControl )
+			{
+				InitRandom( 3 );
+			}
+			else
+			{
+				InitRandom( 5 );
+			}
+			break;
+			
+		case 2:
+			score[0] = score[1] = roundStartScore[0] = roundStartScore[1] = displayedScore[0] = displayedScore[1] = 0;	
+
+			InitRandom( 5 );
+
+			SelectRandomLevel( );
+			InitCharacter( 0, level );
+			
+			SelectRandomLevel( );
+			InitCharacter( 1, level );
+			
+			character[0].hints = (difficulty[0] == kEasyLevel) || (difficulty[0] == kMediumLevel);
+			character[1].hints = (difficulty[1] == kEasyLevel) || (difficulty[1] == kMediumLevel);
+			break;
+	}
+	
+	for( player=0; player<=1; player++ )
+	{
+		for( count=0; count<kGridAcross; count++ )
+		{
+			grays[player][count] = 0;
+			
+			for( count2=0; count2<kGridDown; count2++ )
+			{
+				grid[player][count][count2] = kEmpty;
+				suction[player][count][count2] = kNoSuction;
+				charred[player][count][count2] = kNoCharring;
+				glow[player][count][count2] = false;
+			}
+		}
+		
+		nextA[player] = GetPiece( player );
+		nextB[player] = GetPiece( player );
+		nextM[player] = false;
+		nextG[player] = false;
+		
+		halfway[player] = false;
+		
+		unallocatedGrays[player] = 0;
+		anim[player] = 0;
+		lockGrays[player] = 0;
+		roundStartScore[player] = score[player];
+		
+		RedrawBoardContents(player);
+		
+		if( control[player] != kNobodyControl )
+		{
+			role[player] = kWaitForRetrieval;
+		}
+		else
+		{
+			role[player] = kIdlePlayer;
+		}
+	}
+	
+	PrepareStageGraphics( character[1].picture );
+	if( changeMusic ) ChooseMusic( character[1].music );
+	
+	blobTime[0]     = blobTime[1]     = 
+	boredTime[0]    = boredTime[1]    = 
+	hintTime[0]     = hintTime[1]     =
+	timeAI[0]       = timeAI[1]       =
+	fadeCharTime[0] = fadeCharTime[1] = 
+	messageTime     = startTime       =
+	blinkTime[0]    = blinkTime[1]    = GameTickCount( );
+	
+	blinkTime[1] += 60;
+	
+	if( players == 2 )
+		InitDifficulty( );
+}
+
+void IncrementLevel( void )
+{
+	level++;
+}
+
+void SelectRandomLevel( void )
+{
+	level = RandomBefore( IsRegistered()? kLevels: kSharewareLevels ) + 1;
+}
+
+void InitDifficulty( )
+{
+	MRect blobBoard = { 0, 0, kGridDown * kBlobVertSize, kGridAcross * kBlobHorizSize };
+	int player;
+	const int selectionRow = 5;
+	int count;
+	MRect blobRect;
+	
+	for( player=0; player<=1; player++ )
+	{
+		// Set up variables
+		role[player] = kChooseDifficulty;
+		colorA[player] = RandomBefore(kBlobTypes)+1;
+		colorB[player] = kEmpty;
+		switch( difficulty[player] )
+		{
+			case kEasyLevel:      blobX[player] = 1; break;
+			case kMediumLevel:    blobX[player] = 2; break;
+			case kHardLevel:      blobX[player] = 3; break;
+			case kUltraLevel:     blobX[player] = 4; break;
+		}
+		
+		blobY[player] = selectionRow;
+		blobR[player] = upRotate;
+		blobTime[player] = GameTickCount( ) + (60*8);
+		animTime[player] = GameTickCount( );
+		shadowDepth[player] = kBlobShadowDepth;
+		magic[player] = false;
+		grenade[player] = false;
+		
+		DrawPICTInSurface( boardSurface[player], picSelectDifficulty + backgroundID );
+		
+		SDLU_AcquireSurface( playerSurface[player] );
+
+		SurfaceDrawBoard( player, &blobBoard );
+		
+		grid[player][0][selectionRow] = kGray;
+		suction[player][0][selectionRow] = kEasyGray;
+		charred[player][0][selectionRow] = kNoCharring;
+		CalcBlobRect( 0, selectionRow, &blobRect );
+		SurfaceDrawBlob( player, &blobRect, kGray, kEasyGray, kNoCharring );
+		
+		grid[player][kGridAcross-1][selectionRow] = kGray;	
+		suction[player][kGridAcross-1][selectionRow] = kHardGray;
+		charred[player][kGridAcross-1][selectionRow] = kNoCharring;
+		CalcBlobRect( kGridAcross-1, selectionRow, &blobRect );
+		SurfaceDrawBlob( player, &blobRect, kGray, kHardGray, kNoCharring );
+
+		CalcBlobRect( 1, selectionRow, &blobRect );
+		blobRect.top -= 4; blobRect.bottom += 4;
+		blobRect.left += 4; blobRect.right -= 4;
+		for( count=1; count<=4; count++ )
+		{
+			DrawCharacter( count + '0', &blobRect );
+			OffsetMRect( &blobRect, kBlobHorizSize, 0 );
+		}
+		
+		SDLU_ReleaseSurface( playerSurface[player] );
+		
+		DrawSpriteBlobs( player, kNoSuction );
+		CleanSpriteArea( player, &blobBoard );
+	}
+}
+
+void ChooseDifficulty( int player )
+{
+	MRect blobBoard = { 0, 0, kGridDown * kBlobVertSize, kGridAcross * kBlobHorizSize };
+	const int selectionRow = 5;
+	const int difficultyMap[kGridAcross] = {kEasyLevel, kEasyLevel, kMediumLevel, kHardLevel, kUltraLevel, kUltraLevel};
+	const int fallingSpeed[kGridAcross] = {0, 15, 9, 7, 4, 0};
+	const int startGrays[kGridAcross] = {0, 0,  0, 10, 20, 0};
+	const int difficultyFrame[] = { kNoSuction, blobBlinkAnimation, blobBlinkAnimation,
+									  blobJiggleAnimation, blobCryAnimation, kNoSuction };
+	int oldX = blobX[player];
+	
+	if( !IsRegistered( ) )
+	{
+		if( player == 0 )
+		{
+			HandleDialog( kRegisterDialog );
+			QuickFadeOut( NULL );
+			showStartMenu = true;
+		}	
+
+		return;	
+	}
+	
+	PlayerControl( player );
+	if( blobX[player] != oldX ) anim[player] = 0;
+	
+	UpdateTweak( player, difficultyFrame[blobX[player]] );
+
+	if( GameTickCount( ) >= blobTime[player] )
+	{
+		if( player == 1 && PICTExists( picBoardRight + backgroundID ) )
+		{
+			DrawPICTInSurface( boardSurface[player], picBoardRight + backgroundID );
+		}
+		else
+		{
+			DrawPICTInSurface( boardSurface[player], picBoard + backgroundID );
+		}
+		
+		SDLU_AcquireSurface( playerSurface[player] );
+		SurfaceDrawBoard( player, &blobBoard );
+		SDLU_ReleaseSurface( playerSurface[player] );
+		
+		CleanSpriteArea( player, &blobBoard );
+	
+		grid[player][0][selectionRow] = kEmpty;
+		grid[player][5][selectionRow] = kEmpty;
+												
+		suction[player][0][selectionRow] = kNoSuction;
+		suction[player][5][selectionRow] = kNoSuction;
+		
+		difficulty[player]          = difficultyMap[ blobX[player] ];
+		character[player].dropSpeed = fallingSpeed[ blobX[player] ];
+		unallocatedGrays[player] = lockGrays[player] = startGrays[blobX[player]];
+		character[player].hints     = (startGrays[blobX[player]] == 0);
+		role[player] = kWaitingToStart;
+		
+		PlayStereoFrequency( player, kPause, player );
+	}
+}
+
+const char *gameCredits[][6] = 
+{
+	{ "Programming", "John Stiles", "", "", "", "" },
+	{ "Artwork", "Kate Davis", "Leanne Stiles", "Arnauld de la Grandiere", "Bob Frasure", "Ryan Bliss" },
+	{ "Music", "Leanne Stiles", "fmod", "Lizardking", "Armadon, Explizit", "Leviathan, Nemesis" },
+	{ "Music", "Jester, Pygmy", "Siren", "Sirrus", "Scaven, FC", "Spring" }, 		  
+	{ "Music", "Timewalker", "Jason, Silents", "Chromatic Dragon", "Ng Pei Sin", "" },
+	{ "Open Source", "gcc, mingw", "SDL", "libpng", "IJG", "zlib" },
+	{ "Special Thanks", "Sam Lantinga", "Carey Lening", "modarchive.com", "digitalblasphemy.com", "" },	  
+	{ "Please Register!", "The full version of", "Candy Crisis features", "twelve stages and also", "includes two player", "mode." } 		  
+};
+
+
+void SharewareVictory( void )
+{
+	SkittlesFontPtr textFont, titleFont;
+	SDL_Surface*    backBuffer;
+	SDL_Surface*    frontBuffer;
+	SDL_Rect        creditSrcSDLRect, bufferSrcSDLRect, bufferDstSDLRect;
+	MRect           creditSrcRect = { 0, 0, 369, 150 };
+	MRect           bufferSrcRect = { 0, 50, 480, 300 };
+	MRect           bufferDstRect = { 0, 0, 480, 250 };
+	MPoint          dPoint = { 450, 50 }, lPoint, cPoint;
+	int             scroll, ticks, x, y;
+	int             delay = 2;
+	const char*     text;
+	int             thisFade;
+	const char fade[120] =   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0
+				               1, 2, 3, 4, 5, 6, 7, 8, 9,10, //1
+				              11,12,13,14,15,16,17,18,19,20, //2
+				              20,20,20,20,20,20,20,20,20,20, //3
+				              20,20,20,20,20,20,20,20,20,20, //4
+				              20,20,20,20,20,20,20,20,20,20, //5
+				              20,20,20,20,20,20,20,20,20,20, //6
+				              20,20,20,20,20,20,20,20,20,20, //7
+				              20,20,20,20,20,20,20,20,20,20, //8
+							  20,19,18,17,16,15,14,13,12,11, //9
+							  10, 9, 8, 7, 6, 5, 4, 3, 2, 1, //10
+							   0, 0, 0, 0, 0, 0, 0, 0, 0, 0  //11
+						     };
+	              
+	titleFont = GetFont( picFont );
+	textFont = GetFont( picTinyFont );
+	
+	SDLU_MRectToSDLRect( &creditSrcRect, &creditSrcSDLRect );
+	SDLU_MRectToSDLRect( &bufferSrcRect, &bufferSrcSDLRect );
+	SDLU_MRectToSDLRect( &bufferDstRect, &bufferDstSDLRect );
+	
+	DrawPICTInSurface( frontSurface, picSharewareVictory );
+	SDL_Flip( frontSurface );
+	
+	backBuffer = SDLU_InitSurface( &bufferDstSDLRect, 16 );
+	
+	SDLU_BlitSurface( frontSurface, &bufferSrcSDLRect,
+	                  backBuffer,   &bufferDstSDLRect   );
+
+	frontBuffer = SDLU_InitSurface( &bufferDstSDLRect, 16 );
+	
+	QuickFadeIn( NULL );	
+
+	ChooseMusic( 12 ); 
+	
+	ticks = MTickCount();
+	for( scroll=0; scroll<1500; scroll++ )
+	{
+		SDLU_AcquireSurface( frontBuffer );	
+		
+		SDLU_BlitSurface( backBuffer,   &bufferDstSDLRect,
+		                  frontBuffer,  &bufferDstSDLRect  );
+		
+		ticks += 3;
+		lPoint = dPoint;
+		for( y=0; y<8; y++ )
+		{
+			if( y != 3 && y != 4 )
+			{
+				cPoint.v = lPoint.v + 25;
+				cPoint.h = lPoint.h;
+				if( cPoint.v > 480 ) break;
+				
+				if( cPoint.v > 0 )
+				{
+					text = gameCredits[y][0];
+					thisFade = fade[cPoint.v >> 2];
+					
+					while( *text )
+					{
+						SurfaceBlitWeightedCharacter( titleFont, *text++, &cPoint, 31, 31, 0, thisFade );
+					}
+				}
+				
+				lPoint.v += 50;
+			}
+			
+			for( x=1; x<6; x++ )
+			{
+				if( gameCredits[y][x][0] )
+				{
+					cPoint.v = lPoint.v;
+					cPoint.h = lPoint.h + 20;
+					if( cPoint.v > 480 ) break;
+
+					if( cPoint.v > 0 )
+					{
+						text = gameCredits[y][x];
+						thisFade = fade[cPoint.v >> 2];
+						
+						while( *text )
+						{
+							SurfaceBlitWeightedCharacter( textFont, *text++, &cPoint, 31, 31, 0, thisFade );
+						}
+					}
+
+					lPoint.v += 20;
+				}
+			}
+		}
+		
+		SDLU_ReleaseSurface( frontBuffer );
+
+		dPoint.v--;
+
+		SDLU_BlitFrontSurface( frontBuffer, &bufferDstSDLRect, &bufferSrcSDLRect );
+
+		do
+		{
+			if( SDLU_Button() ) goto out;	
+            SDLU_Yield();	
+		}
+		while( ticks >= MTickCount() );
+	}
+	
+	do
+	{
+        SDLU_Yield();
+	}
+	while( !AnyKeyIsPressed( ) && !SDLU_Button() );
+
+out:
+	QuickFadeOut( NULL );	
+	
+	SDL_FreeSurface( backBuffer );
+	SDL_FreeSurface( frontBuffer );
+}
+
+void RegisteredVictory( void )
+{
+	SkittlesFontPtr textFont, titleFont, bubbleFont;
+	SDL_Surface*    backBuffer;
+	SDL_Surface*    frontBuffer;
+	MPoint          dPoint[] = { { 230, 340 }, { 230, 30 }, { 230, 30 }, { 30, 30 }, { 30, 340 }, { 230, 340 }, { 230, 30 } }; 
+	MPoint          bubblePoint, textPoint, shadowPoint;
+	MPoint          setPoint[7][6];
+	MPoint          msgSetPoint[7][2];
+	long            ticks;
+	int             vertScroll, picture, weight, line, minimum;
+	int             scrollDir[] = {1, -1, 1, -1, 1, -1, -1};
+	int             spacing[] = {40, 19, 19, 19, 23, 19, 23 };
+	const char*     text;
+	SDL_Rect        fullSDLRect = { 0, 0, 640, 480 };
+	SDL_Rect        highSDLRect = { 0, 0, 640, 480 };
+	SDL_Rect        lowSDLRect = { 0, 250, 640, 480 };
+	SDL_Rect        backBufferSDLRect = { 0, 0, 640, 730 };
+	SDL_Rect        scrollSDLRect;
+
+	const char *messages[7][2] =
+	{
+		{ "Congratulations!", "" },
+		{ "You've managed to vaporize all", "of the rampaging candies!" },
+		{ "Your quick thinking and sharp", "reflexes have saved the day." },
+		{ "", "" },
+		{ "", "" },
+		{ "", "" },
+		{ "Thanks for playing Candy Crisis!", "" },
+	};
+	
+	textFont = GetFont( picFont );
+	titleFont = GetFont( picHiScoreFont );
+	bubbleFont = GetFont( picBubbleFont );
+	
+	ChooseMusic( 14 ); 
+
+	for( picture=0; picture<7; picture++ )
+	{
+		for( line=0; line<2; line++ )
+		{
+			msgSetPoint[picture][line].v = ((dPoint[picture].v == 230)? 100: 400) + (line * 30);
+			msgSetPoint[picture][line].h = 320 - (GetTextWidth( titleFont, messages[picture][line] ) / 2);
+		}
+		
+		for( line=0; line<6; line++ )
+		{
+			SkittlesFontPtr font;
+
+			if( line == 0 )
+			{
+				font = titleFont;
+				textPoint.v = 45;				
+			}
+			else
+			{
+				font = textFont;
+				textPoint.v = 65 + (spacing[picture] * line);				
+			}
+
+			textPoint.h = (bubbleFont->width['*'] - GetTextWidth( font, gameCredits[picture][line] )) / 2;
+
+			setPoint[picture][line].v = dPoint[picture].v + textPoint.v;
+			setPoint[picture][line].h = dPoint[picture].h + textPoint.h;
+		}
+		
+		minimum = 640;
+		for( line=1; line<6; line++ )
+		{
+			if( setPoint[picture][line].h < minimum ) minimum = setPoint[picture][line].h;
+		}
+		
+		for( line=1; line<6; line++ )
+		{
+			setPoint[picture][line].h = minimum;
+		}		
+	}
+	
+	backBuffer  = SDLU_InitSurface( &backBufferSDLRect,  16 );
+	frontBuffer = SDLU_InitSurface( &fullSDLRect, 16 );
+	
+	for( picture = 0; picture<7; picture++ )
+	{
+		scrollSDLRect = ( scrollDir[picture] > 0 )? highSDLRect: lowSDLRect;
+		
+		DrawPICTInSurface( backBuffer, picture + picVictory1 );
+
+		SDLU_BlitFrontSurface( backBuffer, &scrollSDLRect, &fullSDLRect );
+		
+		QuickFadeIn( NULL );
+		
+		ticks = MTickCount();
+		for( vertScroll = 0; vertScroll < 250; vertScroll++ )
+		{
+			SDLU_AcquireSurface( frontBuffer );
+					
+			SDLU_BlitSurface( backBuffer,  &scrollSDLRect,
+			                  frontBuffer, &fullSDLRect );
+
+			weight = vertScroll - 20;
+			for( line=0; line<2; line++ )
+			{
+				textPoint = msgSetPoint[picture][line];
+				shadowPoint.v = textPoint.v + 1;
+				shadowPoint.h = textPoint.h + 1;
+				
+				text = messages[picture][line];
+				
+				while( *text && weight > 0 )
+				{
+					int fixedWeight = (weight > 31)? 31: weight;
+					
+					SurfaceBlitWeightedCharacter( titleFont, *text, &shadowPoint, 0,  0,  0,  fixedWeight );
+					SurfaceBlitWeightedCharacter( titleFont, *text, &textPoint,   31, 31, 31, fixedWeight );
+					weight--;
+					text++;
+				}
+			}
+			
+			bubblePoint = dPoint[picture];
+			weight = ( vertScroll <= 210 )? vertScroll - 10: 241 - vertScroll;
+			if( weight < 0  ) weight = 0;
+			if( weight > 31 ) weight = 31;
+			
+			if( weight > 0 )
+			{
+				SurfaceBlitWeightedCharacter( bubbleFont, '*', &bubblePoint, 31, 31, 31, (weight+1)>>1 );
+				
+				for( line=0; line<6; line++ )
+				{
+					SkittlesFontPtr font = (line == 0)? titleFont: textFont;
+					
+					textPoint = setPoint[picture][line];
+					text = gameCredits[picture][line];
+					
+					while( *text )
+					{
+						SurfaceBlitWeightedCharacter( font, *text++, &textPoint, 0, 0, 0, weight );
+					}
+				}
+			}
+			
+			SDLU_ReleaseSurface( frontBuffer );
+			
+			SDLU_BlitFrontSurface( frontBuffer, &fullSDLRect, &fullSDLRect );
+			                       
+			scrollSDLRect.y += scrollDir[picture];
+			
+			ticks += 4;
+			do
+			{ 
+				if( SDLU_Button() ) vertScroll = 250;
+			    SDLU_Yield();
+			}			
+			while( ticks >= MTickCount() );
+		}
+
+		QuickFadeOut( NULL );
+	}
+	
+	SDL_FreeSurface( backBuffer  );
+	SDL_FreeSurface( frontBuffer );
+}
+
+void TotalVictory( void )
+{
+	
+	AddHiscore( score[0] );
+	QuickFadeOut( NULL );	
+
+	DoFullRepaint = NoPaint;
+
+	if( !IsRegistered() )
+	{
+		SharewareVictory( );
+	}
+	else
+	{
+		RegisteredVictory( );
+	}
+
+	showStartMenu = true;
+}
--- /dev/null
+++ b/Source/level.h
@@ -1,0 +1,67 @@
+// level.h
+
+void InitGame( int player1, int player2 );
+MBoolean InitCharacter( int player, int level );
+void PrepareStageGraphics( int type );
+void BeginRound( MBoolean changeMusic );
+void InitDifficulty( void );
+void ChooseDifficulty( int player );
+void SelectRandomLevel( void );
+void IncrementLevel( void );
+void TotalVictory( void );
+void SharewareVictory( void );
+void RegisteredVictory( void );
+void InitStage( void );
+void DrawStage( void );
+void GameStartMenu( void );
+void ShowGameOverScreen( void );
+
+#define kOpponentResource 'Levl'
+
+#define kGlows 2
+
+#define kSharewareLevels 4
+#define kSharewareSolitaireLevels 5
+#define kLevels 12
+#define kTutorialLevel 14
+
+#define kEasyLevel   50
+#define kMediumLevel 70
+#define kHardLevel   90
+#define kUltraLevel  110
+
+
+typedef struct 
+{
+	short color;
+	short time;
+} Glow;
+
+typedef struct
+{
+	short picture;
+	short intellect;
+	short zapStyle;
+	short autoSetup[6];
+	short speedNormal;
+	short speedRush;
+	short music;
+	short dropSpeed;
+	Glow  glow[kGlows];
+	short hints;
+}
+Character;
+
+enum
+{
+	kPlayerControl = 0,
+	kAIControl,
+	kNobodyControl,
+	kAutoControl
+};
+
+extern Character character[2];
+extern int level, players, credits, difficulty[2];
+extern int difficultyTicks, backdropTicks, backdropFrame;
+
+
--- /dev/null
+++ b/Source/main.cpp
@@ -1,0 +1,713 @@
+// main.c
+
+//
+//                      CANDY CRISIS SDL
+//
+// FEATURES:
+// � Runs natively on any platform with support for SDL. Developed 
+//   primarily around the lame Mac Carbon SDL. Can compile and run
+//   with gcc/mingw32 on Windows and gcc on Linux.
+//
+// ETC:
+// ��Game functionality unchanged.
+// � Removed all Mac OS-specific concepts from the game, i.e.:
+//   - GWorlds replaced with SDL_Surfaces.
+//   - Rects and Points replaced with MRects and MPoints. (same contents)
+// ��All resources moved into external files in a folder called
+//   "CandyCrisisResources." Graphics are all JPG and PNG, opened
+//   with SDL_image. Sounds are WAV. Music is still MOD type.
+// � Using the following Open Source projects:
+//   SDL, SDL_image (for graphics)
+//   libjpeg (used by SDL_image)
+//   libpng (used by SDL_image)
+//   zlib (used by libpng)
+// � Using fmod for sound; replaces MikMod
+// ��Wrote a utility library, SDLU, to pick up slack in the SDL 
+//   implementation and to help SDL mesh with a Mac-centric universe.
+// ��New registration code algorithm based on a fast string hash.
+//
+
+//
+//                      CANDY CRISIS X
+//
+// FEATURES:
+// � Runs natively on Mac OS X. Developed around Mac OS X
+// Public Beta 1H39 and 2E14.
+// Updated 3/25/2001 for Mac OS X 4K78--OS X 10.0.
+// Updated 8/25/2001 for Mac OS X 10.0.4.
+//
+// ETC:
+// ��Game functionality unchanged.
+// ��Zerius Sound System scrapped, replaced with LibMikMod. I'm
+// very unhappy with performance relative to ZSS, but on G3s and up,
+// performance should not be a major concern. If only I could get
+// the source to ZSS so it could be Carbonized! 
+// ��Everything runs in one window now, instead of having
+// one window per interface element. This was necessary 
+// because OS X wanted to put drop shadows around everything
+// and it looked pretty weird. This was also a personal pet
+// peeve that I never had the motivation to fix until now.
+// ��Controls dialog is super Aqua savvy, using Theme Text 
+// and Theme Buttons.
+// ��A couple of kludges added, to work around OS X bugs. Ugh.
+// � Found bug which was causing blitter to draw larger dirty rects
+// than necessary (top/left of dirty rect was always 0/0). Not sure 
+// if it ever shipped like that or if this is something I changed 
+// post-Candy Crisis 1.0.
+// ��Changed cursor management since OS X cursors don't know how to
+// hide and show themselves properly.
+//
+// UNRESOLVED:
+// ��Stopped getting Out of Memory reports. I wonder if any of the
+// Candy Crisis cleanups affected this...?
+// ��OS X displays a line of garbage when you try to put up a totally
+// blank cursor. I'm not going to spend too long analyzing this; it's
+// not my bug.
+// 
+
+
+//                    CANDY CRISIS 1.0 UPDATE
+//
+// FEATURES:
+// � Rebranded "Candy Crisis" at the request of Mars Candy Co.
+// Many, many graphical changes as a result. (New logo thanks 
+// to Bob Frasure.)
+// � "Controls" button in main menu per many user requests.
+// � Slightly improved error reporting. 
+// � Option-key at startup to turn on "allow background tasks" 
+// or "don't change resolutions." (Don't change resolutions
+// requires DrawSprocket 1.7.)
+//
+// NONCRITICAL:
+// � Fixed bug in 2P mode where game would say "Player 1 got
+//  best combo!" when Player 2 really got it, and vice versa. 
+// � Changed Magic Skittle ratio to 1/19 instead of 1/17, after
+// watching Brett get tons of Magic Skittles at work. Hmm.
+// ��Replaced RandomBefore with less hacked-up code, because
+// Magic Skittles STILL seemed to be coming up more often than
+// expected. That seemed to take care of it.
+// ��Fixed rare bug where, after losing, the game would sometimes
+// get stuck until you explicitly chose "end game." (Would manifest
+// more often on a slow computer and/or when Background Tasks were
+// activated.)
+//
+// ETC:
+// � Antipiracy measures.
+// ��Game fonts all loaded at startup time instead of dynamically,
+// in an attempt to reduce the number of GWorlds which are created,
+// then torn down, during the game (which could have been potentially
+// fragmenting the heap, though I doubt this was a real problem).
+//
+// UNRESOLVED:
+// ��Still a handful of people who get Out of Memory when they 
+// try to pause a game. Damn. Hopefully now I'll at least know
+// where they're dying (though I highly suspect it's InitGWorld,
+// which without a stack crawl is pretty much useless info...)
+// One guy says this is fixed by deleting prefs. Huh??
+//
+
+//
+//                     2.0.2 UPDATE
+//
+// FEATURES:
+// � When you continue, your score is now rolled back to what
+// it was when you first started the round. This prevents people
+// from racking up high scores by continuing over and over again
+// on the highest board.
+// � You can now clear the high score tables to their default
+// values by holding delete while clicking "high scores" on the
+// main screen.
+//
+// ETC:
+// ��Added small picture to the controls dialog so people know
+// which color is Player 1, and which is Player 2.
+// ��Holding option while warping causes a CPU/CPU match to
+// occur.
+// � Changed in-game registration URL to:
+// http://emulation.net/s2.com/register.html
+//
+// CRITICAL:
+// � Fixed minor memory corruption when a bomb hits floor or
+// gray Skittle. Could potentially have corrupted 3 tiles of
+// opponent's board.
+// ��Fixed bug where potential combo data would not be cleared
+// when choosing "End Game" and then starting a new game, which
+// led to really weird corruptions of potential combo data.
+// ��Fixed bug where holding down button after end-credits rolled
+// would cause the pause dialog to pop up on a zero-gamma screen
+// (whoops).
+// 
+// NONCRITICAL:
+// ��Fixed bug where dropping bomb would not display associated
+// points.
+// ��Fixed bug where dropping bomb on floor/gray Skittle would
+// reward the player for "killing" empty squares, making it
+// score 100x(9-number of grays in 3x3 area) as opposed to
+// the correct 100x(number of blobs in 3x3 area).
+// ��Occasionally, when Best Combo got corrupted, it would show
+// the ending credits instead of displaying the Best Combo. Now
+// there is code to ensure that the level # of the Best Combo 
+// structure is in bounds. (If it isn't, the best combo is
+// assumed to be corrupt, and it is deleted. Not the most 
+// optimal solution, but what can I do?)
+// ��If you started the tutorial, ended it, then viewed the best
+// combo, you'd see a speech balloon appear for one frame.
+// ��Fixed bug where bringing up InputSprocket dialog would 
+// unload ics8's used to draw key caps (damn InputSprocket bugs).
+// Does this only affect ISp < 1.7?
+// � Fixed bug where bringing up InputSprocket dialog would not
+// update game windows behind it after it got closed.
+//
+// UNRESOLVED:
+// � Slow loading time issue seems to only be affecting an
+// incredible minority of people. It's being caused by QuickTime
+// decompressing JPEGs. I think it's not a Skittles 2 issue.
+// ��One guy says if he quits the game, reopens it, starts a game,
+// then pauses it, he gets an Out of Memory condition. He's running
+// 8.6-D clean. Hmm.
+//
+
+//
+//                     2.0.1 UPDATE
+//
+// FEATURES:
+//ʥ�Best Combo
+// ��New bg for level 8
+// ��New sfx for continue sound (requested by Nathan Lamont)
+//
+// ETC:
+// ��High score dialog enhanced to support Best Combo stuff
+// ��Tutorial suggests pressing esc to set up keys now
+// ��More aggressive AI for intellect>18 (does not percieve
+// 1-level zap as advantageous)
+//
+// CRITICAL:
+// ��Fix for out-of-bounds array read (->crash) inside 
+// ZapScoreDisplay.
+// ��Fixed bug where falling Skittles (in DropBlobs) would
+// occasionally have their bottom half lopped off. Tough to
+// see while in motion but totally obvious in screenshots.
+// ��Fixed InputSprocket icons in ISpConfigure dialog
+// ��Workaround for System 7 bug, where setting the 
+// cursor while gamma is faded causes solid black cursor.
+//
+// NONCRITICAL:
+// ��Fixed bug where char fading on blobs that were
+// in motion would cause crap to appear for one frame.
+// Only apparent on slow Macs or in screenshots. Otherwise
+// appeared as flicker and easily dismissed.
+// ��Cmd-tab inside High Scores or Game Over screen
+// no longer leaves a white box (empty window) open
+// and will not switch out with 0 gamma
+// ��Fixed DrawSprocket bug on Macs that can't do 640x480
+// (i.e. PowerBook G3 is stuck at 1024x768). The window
+// would be drawn in lower-right hand corner instead of
+// centered.
+// ��Fixed bug in multipliers for getting multiple 
+// colors at once. (Should have been *3/*6/*12/*24, was
+// actually *3/*9/*21/*45! Ouch!)
+// � Hitting cmd-Q at high score dialog would allow
+// empty high score name to be added to high score table.
+//
+// UNRESOLVED:
+// ��A few users report very slow loading times between
+// levels and during loading sequence. Problem tends to
+// be alleviated by turning on VM. Can't reproduce here.
+// 
+
+//
+//                 2.0.0 INITIAL RELEASE
+//
+
+#if _WIN32
+#include <windows.h>
+#include <io.h> // for _chdir
+#endif
+
+#include "SDL.h"
+#include "SDLU.h"
+#include "SDL_image.h"
+
+#include "main.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "hiscore.h"
+#include "control.h"
+#include "players.h"
+#include "gworld.h"
+#include "graphics.h"
+#include "grays.h"
+#include "soundfx.h"
+#include "next.h"
+#include "random.h"
+#include "victory.h"
+#include "score.h"
+#include "graymonitor.h"
+#include "music.h"
+#include "gameticks.h"
+#include "level.h"
+#include "opponent.h"
+#include "keyselect.h"
+#include "blitter.h"
+#include "prefs.h"
+#include "tweak.h"
+#include "zap.h"
+#include "pause.h"
+#include "tutorial.h"
+#include "RegAlgorithm.h"
+
+
+SDL_Surface* frontSurface;
+signed char  nextA[2], nextB[2], nextM[2], nextG[2], colorA[2], colorB[2],
+	         blobX[2], blobY[2], blobR[2], blobSpin[2], speed[2], role[2], halfway[2],
+	         control[2], dropping[2], magic[2], grenade[2], anim[2];
+int          chain[2];
+long         blobTime[2], startTime, endTime;
+MBoolean     finished = false, pauseKey = false, showStartMenu = true;
+signed char  grid[2][kGridAcross][kGridDown], suction[2][kGridAcross][kGridDown], charred[2][kGridAcross][kGridDown], glow[2][kGridAcross][kGridDown];
+MRect        playerWindowZRect, playerWindowRect[2];
+MBoolean     playerWindowVisible[2] = { true, true };
+KeyList      hitKey[2];
+int          backgroundID = -1;
+MPoint       blobWindow[8][2];
+MBoolean     playerIsRegistered = false;
+char         registeredName[64] = "";
+char         registeredKey[18] = ""; // size is strange just to perplex hackers
+void         (*DoFullRepaint)() = NoPaint;
+MBoolean     needsRefresh = false;
+
+static char  candyCrisisResources[512];
+
+int main(int argc, char *argv[])
+{
+	argc, argv;
+	
+	Initialize( );	
+	if( IsRegistered( ) ) exit(0);
+
+	LoadPrefs( );
+	
+	ReserveMonitor( );	
+	ShowTitle( );
+
+	if( !IsRegistered( ) ) 
+	{
+		SDLU_SetBrightness( 1.0 );
+		SharewareNotice( 15*30 );
+		SDLU_SetBrightness( 0.0 );
+	}
+	
+	ChooseMusic( 13 );
+	
+	while( !finished )
+	{
+		if( showStartMenu )
+		{
+			GameStartMenu( );
+			showStartMenu = false;
+		}
+		
+		if( !finished )
+		{
+			DoFullRepaint = NeedRefresh;
+			CheckKeys( );
+			HandlePlayers( );
+			UpdateOpponent( );
+			UpdateBalloon( );
+			UpdateSound( );
+			DoFullRepaint = NoPaint;
+			
+			if( needsRefresh )
+			{
+				RefreshAll();
+				needsRefresh = false;
+			}
+			
+			if( !showStartMenu && pauseKey )
+			{
+				FreezeGameTickCount( );
+				PauseMusic( );
+				MaskRect( &playerWindowRect[0] );
+				MaskRect( &playerWindowRect[1] );
+				WaitForRelease( );
+				
+				HandleDialog( kPauseDialog );
+								
+				WaitForRelease( );
+				RefreshPlayerWindow( 0 );
+				RefreshPlayerWindow( 1 );
+				ResumeMusic( );
+				UnfreezeGameTickCount( );
+			}
+		}
+	}
+	
+	SavePrefs( );
+	ReleaseMonitor( );
+	
+	return 0;
+}
+
+void NoPaint( void )
+{
+}
+
+void MaskRect( MRect *r )
+{
+	SDL_Rect sdlRect;
+	SDLU_MRectToSDLRect( r, &sdlRect );
+	SDLU_BlitFrontSurface( backdropSurface, &sdlRect, &sdlRect );
+}
+
+void RefreshPlayerWindow( short player )
+{
+	MRect fullUpdate = {0, 0, kGridDown * kBlobVertSize, kGridAcross * kBlobHorizSize };
+	
+	if( control[player] == kNobodyControl )
+	{
+		MaskRect( &playerWindowRect[player] );
+	}
+	else
+	{
+		SetUpdateRect( player, &fullUpdate );
+		UpdatePlayerWindow( player );
+	}
+}
+
+void NeedRefresh()
+{
+	needsRefresh = true;
+}
+
+void RefreshAll( void )
+{	
+	DrawBackdrop( );
+
+	ShowGrayMonitor( 0 );
+	ShowGrayMonitor( 1 );
+
+	RefreshNext( 0 );
+	RefreshNext( 1 );
+
+	RefreshPlayerWindow( 0 );
+	RefreshPlayerWindow( 1 );
+
+	DrawFrozenOpponent( );
+	DrawStage( );
+
+	ShowScore( 0 );
+	ShowScore( 1 );
+}
+
+void Error( const char* extra )
+{
+#if TARGET_API_MAC_CARBON
+	Str255 myString, extraP;
+	
+	CopyCStringToPascal( extra, extraP );
+	ReleaseMonitor( );
+	GetIndString( myString, 131, errUnknown );
+	ParamText( myString, extraP, "\p", "\p" );
+	Alert( dFatalErrorAlert, NULL );
+	ExitToShell( );
+#else
+	char message[256];
+	sprintf( message, "Sorry, a critical error has occurred. Please report the following error message:\n    %s", extra );
+	#if WIN32
+		MessageBox( NULL, message, "Candy Crisis", MB_OK );
+	#else
+		fprintf(stderr, "Candy Crisis: %s\n", message);
+	#endif
+	exit(0);
+#endif
+}
+
+void WaitForRelease( void )
+{	
+	do
+	{
+		SDLU_Yield();
+	}
+	while( AnyKeyIsPressed( ) || SDLU_Button() );
+}
+
+MBoolean AnyKeyIsPressed( void )
+{
+	int index;
+	int arraySize;
+	unsigned char* pressedKeys;
+				                 
+	SDLU_PumpEvents();          
+	pressedKeys = SDL_GetKeyState( &arraySize );
+	
+	// Only check ASCII keys. (Reason: some extended keys, like NUMLOCK or CAPSLOCK,
+	// can be on all the time even if a key really isn't depressed.)
+	if( arraySize > 128 ) arraySize = 128;
+	
+	for( index = 0; index < arraySize; index++ )
+	{
+		if( pressedKeys[index] ) 
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+
+MBoolean ControlKeyIsPressed( void )
+{
+	int arraySize;
+	unsigned char* pressedKeys;
+				                 
+	SDLU_PumpEvents();          
+	pressedKeys = SDL_GetKeyState( &arraySize );
+
+	return pressedKeys[ SDLK_LCTRL ] || pressedKeys[ SDLK_RCTRL ];
+}
+
+MBoolean OptionKeyIsPressed( void )
+{
+	int arraySize;
+	unsigned char* pressedKeys;
+				                 
+	SDLU_PumpEvents();          
+	pressedKeys = SDL_GetKeyState( &arraySize );
+
+	return pressedKeys[ SDLK_LALT ] || pressedKeys[ SDLK_RALT ];
+}
+
+void RetrieveResources( void )
+{
+	                            OpeningProgress( 0, 10 );
+	InitSound( );				OpeningProgress( 1, 10 );
+
+	InitBackdrop( );			OpeningProgress( 2, 10 );
+
+	GetBlobGraphics( );			OpeningProgress( 3, 10 );
+	
+	InitNext( );				OpeningProgress( 4, 10 );
+	
+	InitScore( );				OpeningProgress( 5, 10 );
+
+	InitRegistration();
+	InitGrayMonitors( );		OpeningProgress( 6, 10 );
+	
+	InitOpponent( );			OpeningProgress( 7, 10 );
+
+	InitStage( );   // must run after backdrop window is open
+	InitGameTickCount( );
+
+	InitPlayers( ); // must run after backdrop window is open
+	InitFont( ); 
+	InitZapStyle( );// must run after fonts are inited
+					            OpeningProgress( 8, 10 );
+	
+	InitBlitter( ); // must run after player windows are open
+	InitPlayerWorlds( );  		OpeningProgress( 9, 10 );
+	
+	InitVictory( );	// must run after fonts are inited			
+	InitTweak( );				OpeningProgress( 10, 10 );
+}
+
+
+void CenterRectOnScreen( MRect *rect, double locationX, double locationY )
+{
+	MPoint dest = {0,0};
+	
+	dest.h = (short)(locationX * (640 - (rect->right - rect->left)));
+	dest.h &= ~3;
+	dest.v = (short)(locationY * (480 - (rect->bottom - rect->top)));
+
+	OffsetMRect( rect, -rect->left, -rect->top );
+	OffsetMRect( rect, dest.h, dest.v );
+}
+
+void ReserveMonitor( void )
+{
+	SDL_Surface* icon;
+	SDL_Surface* mask;
+	
+	icon = LoadPICTAsSurface( 10000, 16 );
+	mask = LoadPICTAsSurface( 10001, 1 );
+	SDL_WM_SetIcon( icon, (Uint8*) mask->pixels );
+	SDL_FreeSurface( icon );
+	SDL_FreeSurface( mask );
+
+	SDL_ShowCursor( SDL_DISABLE );
+	
+#if TARGET_API_MAC_CARBON
+	frontSurface = SDL_SetVideoMode( 640, 480, 15, SDL_SWSURFACE );
+#else
+	frontSurface = SDL_SetVideoMode( 640, 480, 16, SDL_SWSURFACE | SDL_FULLSCREEN );
+#endif
+
+	SDL_WM_SetCaption( "Candy Crisis", "CandyCrisis" );
+}
+
+void ReleaseMonitor( void )
+{
+	// frontSurface is released by SDL_Quit... we are not supposed to kill it
+}
+
+int Warp( void )
+{
+	return 8;
+}
+
+const char* QuickResourceName( const char* prefix, int id, const char* extension )
+{
+	static char name[512];
+	if( id ) 
+	{
+		sprintf( name, "%s%s_%d%s", candyCrisisResources, prefix, id, extension );
+	}
+	else
+	{
+		sprintf( name, "%s%s%s", candyCrisisResources, prefix, extension );
+	}
+	
+	return name;
+}
+
+void Initialize( void )
+{	
+#if _WIN32
+    HMODULE module;
+    char    name[MAX_PATH+1], *lastBackslash;
+   
+    module = GetModuleHandle( NULL );
+    GetModuleFileName( module, name, MAX_PATH );
+    lastBackslash = strrchr( name, '\\' );
+    if( lastBackslash != NULL )
+    {
+        *lastBackslash = '\0';
+        strcpy( candyCrisisResources, name );
+        strcat( candyCrisisResources, "\\CandyCrisisResources\\" );
+    }
+#endif
+#if TARGET_API_MAC_CARBON
+	strcpy( candyCrisisResources, ":CandyCrisisResources:" );
+#endif
+#ifdef linux
+	strcpy( candyCrisisResources, "CandyCrisisResources/" );
+#endif
+
+	if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO ) < 0 )
+	{
+		Error( "SDL_Init failed" );
+	}
+	
+	atexit( SDL_Quit );
+	
+	SDL_SetEventFilter( SDLU_EventFilter );
+}
+
+void LaunchURL( const char* url )
+{
+#if TARGET_API_MAC_CARBON
+	OSStatus err = -1;
+	ICInstance inst;
+	long startSel;
+	long endSel;
+
+	if( ICStart != NULL )
+	{
+		err = ICStart( &inst, 'Skit' );
+		if (err == noErr)
+		{
+			startSel = 0;
+			endSel = strlen(url);
+			err = ICLaunchURL( inst, "\p", url, strlen(url), &startSel, &endSel );
+			ICStop(inst);
+		}
+	}
+#else
+    SDL_WM_IconifyWindow();
+	ShellExecute( NULL, "open", url, "", "c:\\", SW_SHOWNORMAL );
+	WaitForRegainFocus();
+#endif
+}
+
+void QuickFadeIn( MRGBColor *color )
+{
+	color; // is unused
+
+#ifndef TARGET_API_MAC_CARBON
+	long  c;
+	float percent;
+
+	for( percent=0.0f; percent<1.0f; percent += 0.04f )
+	{
+		c = MTickCount( ); 
+		SDLU_SetBrightness( percent );
+		while( c == MTickCount( ) ) 
+		{  
+			SDLU_Yield(); 
+		}
+	}
+	
+	SDLU_SetBrightness( percent );
+#endif
+}
+
+void QuickFadeOut( MRGBColor *color )
+{
+	color; // is unused
+
+#ifndef TARGET_API_MAC_CARBON
+	long   c;
+	float  percent;
+
+	for( percent=1.0f; percent>0.0f; percent -= 0.04f )
+	{
+		c = MTickCount( ); 
+		SDLU_SetBrightness( percent );
+ 		while( c == MTickCount( ) )
+ 		{
+ 			SDLU_Yield(); 
+ 		}
+	}
+	
+	SDLU_SetBrightness( percent );
+#endif
+}
+
+MBoolean FileExists( const char* name )
+{
+	FILE* f = fopen( name, "rb" );
+	if( f == NULL )
+	{
+		return false;
+	}
+	
+	fclose( f );
+	return true;
+}
+
+
+void WaitForRegainFocus()
+{
+    do
+    {  
+        SDLU_PumpEvents();
+        SDL_Delay(50);
+    }
+    while( !SDLU_IsForeground() );    
+
+	DoFullRepaint();
+}
+
+
+void InitRegistration()
+{
+	playerIsRegistered = ValidateCode( registeredName, registeredKey );
+}
+
+
+MBoolean IsRegistered()
+{
+	return playerIsRegistered;
+}
--- /dev/null
+++ b/Source/main.h
@@ -1,0 +1,295 @@
+// main.h
+
+
+#include "SDL.h"
+#include "MTypes.h"
+
+
+void Initialize( void );
+void RetrieveResources( void );
+void Error( const char* extra );
+void CenterRectOnScreen( MRect *rect, double locationX, double locationY );
+MBoolean AnyKeyIsPressed( void );
+void RefreshAll( void );
+void ReserveMonitor( void );
+void ReleaseMonitor( void );
+void MaskRect( MRect *r );
+void RefreshPlayerWindow( short player );
+MBoolean IsRegistered( void );
+int Warp( void );
+void WaitForRelease( void );
+void LaunchURL( const char* url );
+void QuickFadeIn( MRGBColor* color );
+void QuickFadeOut( MRGBColor* color );
+MBoolean FileExists( const char* name );
+void GoToBackground();
+MBoolean ControlKeyIsPressed( void );
+MBoolean OptionKeyIsPressed( void );
+MBoolean IsRegistered();
+void InitRegistration();
+void NoPaint();
+void NeedRefresh();
+const char* QuickResourceName( const char* prefix, int id, const char* extension );
+void WaitForRegainFocus();
+
+
+#define flashyAnimation true
+
+
+typedef struct
+{
+	short left, right, drop, rotate;
+} KeyList;
+
+
+#define kGridAcross 6
+#define kGridDown 13
+
+enum
+{
+	eNoState = 0,
+	eStartMenu,
+	eInGame,
+	eInGameOver,
+	eSharewareVictory,
+	eRegisteredVictory,
+	eFinished
+};
+
+
+enum
+{
+	dFatalErrorAlert = 128,
+	dAbout = 130,
+	dSerialNumber,
+	dShareware,
+	dInformation,
+	dRegister,
+	dSoundTest,
+	dWarp,
+	dOptions = 500,
+	dKeySelect = 800
+};
+
+enum
+{
+	mBar = 128,
+	mApple = 128,
+	mFile,
+	iAbout = 1,
+	iQuit = 1
+};
+
+enum
+{
+	picBoard = 1000,
+	picBackdrop,
+	picNext,
+	picVictory,
+	picSelectDifficulty,
+	picBoardRight,
+	picBlob = 200,
+	picBlobMask,
+	picCharMask,
+	picNumber,
+	picNumberMask,
+	picBlast,
+	picBlastMask,
+	picFont = 250,
+	picHiScoreFont,
+	picContinueFont,
+	picBalloonFont,
+	picZapFont,
+	picZapOutlineFont,
+	picVictoryFont,
+	picBubbleFont,
+	picTinyFont,
+	picDashedLineFont,
+	picBatsuFont,
+	picTitle = 300,
+	picSharewareVictory,
+	picGameStart,
+	picGameOver,
+	picVictory1,
+	picVictory2,
+	picVictory3,
+	picVictory4,
+	picVictory5,
+	picVictory6,
+	picLogo = 500,
+	picLogoAlpha,
+	picLogoMask
+};
+
+enum
+{
+	errNoMonitor = 1,
+	errNoDrawSprocket,
+	errNoMemory,
+	errNoQuickTime,
+	errUnknown
+};
+
+enum
+{
+	winPlayer = 128,
+	winBackdrop,
+	winNext,
+	winScore,
+	winOpponent,
+	winTitle,
+	winVictory,
+	winLevel
+};
+
+enum
+{
+	rightRotate = 0,
+	downRotate,
+	leftRotate,
+	upRotate
+};
+
+enum
+{
+	kEmpty = 0,
+	kBlob,
+	kBlob2,
+	kBlob3,
+	kBlob4,
+	kBlob5,
+	kBlob6,
+	kBlob7,
+	kBombTop,
+	kBombBottom,
+	kGray,
+	kLight,
+	kSun
+};
+
+enum
+{
+	kNoSuction		 = 0,
+	kUp				 = 1,
+	kRight			 = 2,
+	kUpRight		 = 3,
+	kDown			 = 4,
+	kUpDown			 = 5,
+	kRightDown		 = 6,
+	kUpRightDown	 = 7,
+	kLeft			 = 8,
+	kLeftUp			 = 9,
+	kLeftRight 		 = 10,
+	kLeftUpRight	 = 11,
+	kLeftDown		 = 12,
+	kLeftUpDown		 = 13,
+	kLeftRightDown	 = 14,
+	kLeftUpRightDown = 15,
+	kDying			 = 16,
+	kSquish			 = 17,
+	kSquash 		 = 18,
+	kSquish1         = 19,
+	kSquish2         = 20,
+	kSquish3         = 21,
+	kSquish4         = 22,
+	kBlinkBlob       = 23,
+	kSobBlob         = 24,
+	kSob2Blob        = 25,
+	kFlashDarkBlob   = 26,
+	kFlashBrightBlob = 27,
+	kJiggle1         = 28,
+	kJiggle2         = 29,
+	kJiggle3		 = 30, 
+	kJiggle4		 = 31,  
+	kJiggle5		 = 32, 
+	kJiggle6		 = 33, 
+	kJiggle7		 = 34, 
+	kJiggle8		 = 35, 
+	kInDoubt		 = 36,
+	kInDeath		 = 37
+};
+
+enum
+{
+	kNoCharring      = 0,
+	kBombFuse1       = 0,
+	kBombFuse2       = 1,
+	kBombFuse3       = 2,
+	kBlinkBomb1      = 3,
+	kBlinkBomb2      = 4,
+	kBlinkBomb3      = 5,
+	kChar11,
+	kChar31,
+	kChar12, 
+	kChar32, 
+	kChar13, 
+	kChar33, 
+	kChar14, 
+	kChar24, 
+	kChar34
+};
+
+enum
+{
+	kDarkChar = 0xF0,
+	kLightestChar = 0x00
+};
+
+enum 
+{
+	kFlashAnimation  = 0,
+	kJiggleAnimation
+};
+
+#define kBlobFrames (kFlashBrightBlob+1)
+
+enum
+{
+	kGrayNoBlink = 0,
+	kGrayBlink1,
+	kGrayBlink2,
+	kGrayBlink3,
+	kSunGlow1,
+	kSunGlow2,
+	kSunGlow3,
+	kSunGlow4,
+	kSmallGray1,
+	kSmallGray1b,
+	kSmallGray2,
+	kSmallGray2b,
+	kSmallGray3,
+	kSmallGray3b,
+	kSmallGray4,
+	kSmallGray4b,
+	kSmallGray5,
+	kSmallGray5b,
+	kEasyGray = 25,
+	kHardGray,
+	kStageGray
+};
+#define kGrayFrames (kGrayDying4+1)
+
+#define kFirstBlob kBlob
+#define kLastBlob kBlob7
+#define kBlobTypes (kLastBlob - kFirstBlob + 1)
+
+#define pi 3.1415926535898
+
+extern SDL_Surface* frontSurface;
+
+extern signed char nextA[2], nextB[2], nextM[2], nextG[2], colorA[2], colorB[2],
+	blobX[2], blobY[2], blobR[2], blobSpin[2], speed[2], role[2], halfway[2],
+	control[2], dropping[2], magic[2], grenade[2], anim[2];
+extern int chain[2];
+extern long blobTime[2], startTime, endTime;
+extern MBoolean finished, pauseKey, showStartMenu;
+extern signed char grid[2][kGridAcross][kGridDown], suction[2][kGridAcross][kGridDown],
+	charred[2][kGridAcross][kGridDown], glow[2][kGridAcross][kGridDown];
+extern MRect playerWindowZRect, playerWindowRect[2];
+extern MBoolean playerWindowVisible[2];
+extern KeyList hitKey[2];
+extern int backgroundID;
+extern MBoolean playerIsRegistered;
+extern char registeredName[64];
+extern char registeredKey[18];
+extern void (*DoFullRepaint)();
--- /dev/null
+++ b/Source/moving.cpp
@@ -1,0 +1,208 @@
+// moving.c
+
+#include "main.h"
+#include "moving.h"
+#include "players.h"
+#include "graphics.h"
+#include "soundfx.h"
+#include "tweak.h"
+#include "gameticks.h"
+#include "level.h"
+
+void CalcSecondBlobOffset( int player, int *x, int *y )
+{
+	*x = *y = 0;
+	
+	switch( blobR[player] )
+	{
+		case rightRotate:
+			*x = 1;
+			break;
+		
+		case downRotate:
+			*y = 1;
+			break;
+			
+		case leftRotate:
+			*x = -1;
+			break;
+		
+		case upRotate:
+			*y = -1;
+			break;
+	}
+}
+
+MBoolean CanGoLeft( int player )
+{
+	return CanMoveDirection( player, -1, halfway[player]? 1: 0 );
+}
+
+void GoLeft( int player )
+{
+	EraseSpriteBlobs( player );
+	blobX[player]--;
+	StartTweak( player, -1, 0, 0 );
+	DrawSpriteBlobs( player, kNoSuction );
+	
+	PlayStereo( player, kShift );
+}
+
+MBoolean CanGoRight( int player )
+{
+	return CanMoveDirection( player, 1, halfway[player]? 1: 0 );
+}
+
+void GoRight( int player )
+{
+	EraseSpriteBlobs( player );
+	blobX[player]++;
+	StartTweak( player, 1, 0, 0 );
+	DrawSpriteBlobs( player, kNoSuction );
+	
+	PlayStereo( player, kShift );
+}
+
+MBoolean CanFall( int player )
+{
+	return CanMoveDirection( player, 0, 1 );
+}
+
+MBoolean CanMoveDirection( int player, int dirX, int dirY )
+{
+	int currentX = blobX[player], currentY = blobY[player], x, y;
+	
+	currentX += dirX;
+	currentY += dirY;
+	
+	if( currentX < 0 || currentX >= kGridAcross || currentY >= kGridDown )
+		return false;
+	
+	if( currentY >= 0 )
+		if( grid[player][currentX][currentY] != kEmpty )
+			return false;
+
+	CalcSecondBlobOffset( player, &x, &y );
+	
+	currentX += x;
+	currentY += y;
+	
+	if( currentX < 0 || currentX >= kGridAcross || currentY >= kGridDown )
+		return false;
+	
+	if( currentY >= 0 )
+		if( grid[player][currentX][currentY] != kEmpty )
+			return false;
+
+	return true;
+}
+
+void DoFall( int player )
+{
+	EraseSpriteBlobs( player );
+	
+	if( halfway[player] )
+		blobY[player]++;
+	halfway[player] = !halfway[player];
+	
+	StartTweak( player, 0, 0, 1 );
+
+	DrawSpriteBlobs( player, kNoSuction );
+}
+
+MBoolean CanRotate( int player )
+{
+	if( role[player] == kChooseDifficulty ) return false;
+	
+	if( grenade[player] ) return false;
+	
+	return true;
+	
+}
+
+void DoRotate( int player )
+{
+	MBoolean possible;
+	
+	EraseSpriteBlobs( player );
+	
+	blobR[player] = ( blobR[player] + 1 ) % 4;
+	possible = CanMoveDirection( player, 0, halfway[player]? 1: 0 );
+	StartTweak( player, 0, 1, 0 ); // only rotates clockwise
+	
+	if( !possible )
+	{
+		if( blobR[player] == downRotate )
+		{
+			if( halfway[player] )
+				halfway[player] = false;
+			else
+				blobY[player]--;
+				
+			if( ++blobSpin[player] >= 4 )
+			{
+				blobTime[player] = animTime[player] = GameTickCount( );
+				role[player] = kLockdownBlobs;
+				anim[player] = 0;
+				PlayStereoFrequency( player, kPlace, player );
+			}
+		}
+		
+		if( blobR[player] == leftRotate )
+		{
+			if( CanGoRight(player) )
+				GoRight( player );
+			else
+			{
+				blobR[player]++;
+				StartTweak( player, 0, 2, 0 );
+			}
+		}
+		
+		if( blobR[player] == rightRotate  )
+		{
+			if( CanGoLeft(player) )
+				GoLeft( player );
+			else
+			{
+				blobR[player]++;
+				StartTweak( player, 0, 2, 0 );
+				
+				if( !CanMoveDirection( player, 0, halfway[player]? 1: 0 ) )
+				{
+					if( halfway[player] )
+						halfway[player] = false;
+					else
+						blobY[player]--;
+
+					if( ++blobSpin[player] >= 4 )
+					{
+						blobTime[player] = animTime[player] = GameTickCount( );
+						role[player] = kLockdownBlobs;
+						anim[player] = 0;
+						PlayStereoFrequency( player, kPlace, player );
+					}
+				}
+			}
+		}
+	}
+	
+	DrawSpriteBlobs( player, kNoSuction );
+	
+	PlayStereo( player, kRotate );
+}
+
+void DoDrop( int player )
+{
+	dropping[player] = true;
+	
+	if( role[player] != kJiggleBlobs &&
+		role[player] != kFastJiggleBlobs &&
+		role[player] != kLockdownBlobs      )
+		blobTime[player] = GameTickCount( );
+}
+
+void StopDrop( int player )
+{
+	dropping[player] = false;
+}
--- /dev/null
+++ b/Source/moving.h
@@ -1,0 +1,14 @@
+// moving.h
+
+MBoolean CanMoveDirection( int player, int dirX, int dirY );
+void CalcSecondBlobOffset( int player, int *x, int *y );
+MBoolean CanGoLeft( int player );
+void GoLeft( int player );
+MBoolean CanGoRight( int player );
+void GoRight( int player );
+MBoolean CanFall( int player );
+void DoFall( int player );
+MBoolean CanRotate( int player );
+void DoRotate( int player );
+void DoDrop( int player );
+void StopDrop( int player );
--- /dev/null
+++ b/Source/music.h
@@ -1,0 +1,15 @@
+// music.h
+
+
+void EnableMusic( MBoolean on );
+void PauseMusic( void );
+void ResumeMusic( void );
+void FastMusic( void );
+void SlowMusic( void );
+void ChooseMusic( short which );
+
+#define kSongs 14
+
+extern MBoolean musicOn;
+extern int      musicSelection;
+
--- /dev/null
+++ b/Source/next.cpp
@@ -1,0 +1,208 @@
+// next.c
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include "main.h"
+#include "next.h"
+#include "graphics.h"
+#include "gworld.h"
+#include "gameticks.h"
+#include "random.h"
+#include "blitter.h"
+#include "level.h"
+
+#define kJiggleFrames 8
+#define kPulling 10
+#define kPullingFrames 18
+
+SDL_Surface* nextSurface;
+SDL_Surface* nextDrawSurface;
+
+MRect nextWindowZRect, nextWindowRect[2];
+MBoolean nextWindowVisible[2] = {true, true};
+int nextTime[2][2], nextStage[2][2], pullA[2], pullB[2];
+
+void InitNext( void )
+{
+	const double windowLoc[] = {0.46, 0.54};
+	SDL_Rect     sdlRect;
+
+	nextWindowZRect.top = nextWindowZRect.left = 0;
+	nextWindowZRect.bottom = 72; nextWindowZRect.right = 32;
+	
+	nextWindowRect[0] = nextWindowRect[1] = nextWindowZRect;
+	CenterRectOnScreen( &nextWindowRect[0], windowLoc[0], 0.25 );
+	CenterRectOnScreen( &nextWindowRect[1], windowLoc[1], 0.25 );	
+	
+	nextSurface = LoadPICTAsSurface( picNext, 16 );
+	
+	nextDrawSurface = SDLU_InitSurface( SDLU_MRectToSDLRect( &nextWindowZRect, &sdlRect ), 16 );
+}
+
+void RefreshNext( int player )
+{
+	nextStage[player][0] = 0;
+    nextStage[player][1] = 0;
+
+	nextTime[player][0] = GameTickCount( ) + RandomBefore( 60 );
+	nextTime[player][1] = GameTickCount( ) + RandomBefore( 60 );
+
+	ShowNext( player );
+}
+
+void PullNext( int player )
+{
+	pullA[player] = nextA[player];
+	pullB[player] = nextB[player];
+	nextStage[player][0] = kPulling;
+	nextTime[player][0] = GameTickCount( );
+}
+
+#define kNoDraw 999
+void ShowPull( int player )
+{
+	MRect    srcRect;
+	int      yank[8] = { 20, 18, 15, 8, -6, -26, -46, -66 };
+	int      slide[8] = { kNoDraw, 66, 48, 36, 29, 26, 24, 23 };
+	int      drawA, drawB, offset, count;
+	SDL_Rect sourceSDLRect, destSDLRect;
+	
+	if( !nextWindowVisible[player] ) return;
+	
+	SDLU_AcquireSurface( nextDrawSurface );
+	
+	SDLU_BlitSurface( nextSurface,     &nextSurface->clip_rect,
+					  nextDrawSurface, &nextDrawSurface->clip_rect );
+	
+	for( count=0; count<2; count++ )
+	{
+		offset = nextStage[player][0] - kPulling;
+		
+		switch( count )
+		{
+			case 0: drawA = pullA[player]; drawB = pullB[player]; offset = yank[offset];  break;
+			case 1: drawA = nextA[player]; drawB = nextB[player]; offset = slide[offset]; break;
+		}
+		
+		if( offset != kNoDraw )
+		{
+			MRect blobRect = { 0, 4, 0 + kBlobVertSize, 4 + kBlobHorizSize };
+			MRect shadowRect = { 4, 8, 4 + kBlobVertSize, 8 + kBlobHorizSize };
+
+			OffsetMRect( &blobRect, 0, offset );
+			OffsetMRect( &shadowRect, 0, offset );
+					
+			SurfaceDrawShadow( &shadowRect, drawB, kNoSuction );
+			
+			CalcBlobRect( kNoSuction, drawB-1, &srcRect );
+			SurfaceBlitBlob( &srcRect, &blobRect );	  
+					  
+			OffsetMRect( &blobRect, 0, kBlobVertSize );
+			OffsetMRect( &shadowRect, 0, kBlobVertSize );
+			
+			SurfaceDrawShadow( &shadowRect, drawA, nextM[player]? kFlashDarkBlob: kNoSuction );
+
+			CalcBlobRect( nextM[player]? kFlashDarkBlob: kNoSuction, drawA-1, &srcRect );
+			SurfaceBlitBlob( &srcRect, &blobRect );	  
+		}
+	}
+	
+	SDLU_ReleaseSurface( nextDrawSurface );
+	
+	SDLU_BlitFrontSurface( nextDrawSurface, 
+	                       SDLU_MRectToSDLRect( &nextWindowZRect, &sourceSDLRect ),
+	                       SDLU_MRectToSDLRect( &nextWindowRect[player], &destSDLRect ) );
+}
+
+void UpdateNext( int player )
+{
+	MBoolean changed = false;
+	int blob;
+	
+	if( nextStage[player][0] >= kPulling )
+	{
+		if( GameTickCount() > nextTime[player][0] )
+		{
+			if( ++nextStage[player][0] >= kPullingFrames )
+			{
+				RefreshNext( player );
+			}
+			else
+			{
+				ShowPull( player );
+				nextTime[player][0]++;
+			}
+		}
+	} 
+	else
+	{
+		for( blob=0; blob<2; blob++ )
+		{	
+			if( GameTickCount() > nextTime[player][blob] )
+			{
+				if( ++nextStage[player][blob] >= kJiggleFrames )
+				{
+					nextStage[player][blob] = 0;
+					nextTime[player][blob] += 40 + RandomBefore( 80 );
+				}
+				else
+				{
+					nextTime[player][blob] += 2;
+				}
+					
+				changed = true;
+			}
+		}
+		
+		if( changed ) ShowNext( player );
+	}
+}
+
+void ShowNext( int player )
+{
+	int      jiggle[kJiggleFrames] = { kNoSuction,  kSquish,  kNoSuction,  kSquash,    
+	                                   kNoSuction,  kSquish,  kNoSuction,  kSquash   };
+	int      nextFrame = kNoSuction;
+	MRect    blobRect = { 22, 4, 22 + kBlobVertSize, 4 + kBlobHorizSize };
+	MRect    shadowRect = { 26, 8, 26 + kBlobVertSize, 8 + kBlobHorizSize };
+	MRect    srcRect;
+	SDL_Rect sourceSDLRect, destSDLRect;
+	
+	if( !nextWindowVisible[player] ) return;
+
+	if( control[player] == kNobodyControl )
+	{
+	}
+	else
+	{
+		SDLU_AcquireSurface( nextDrawSurface );
+
+		SDLU_BlitSurface( nextSurface,     &nextSurface->clip_rect,
+						  nextDrawSurface, &nextDrawSurface->clip_rect );
+				
+		nextFrame = nextG[player]? kNoSuction: jiggle[nextStage[player][0]];
+		
+		SurfaceDrawShadow( &shadowRect, nextB[player], nextFrame );
+		
+		CalcBlobRect( nextFrame, nextB[player]-1, &srcRect );
+		SurfaceBlitBlob( &srcRect, &blobRect );	  
+				  
+		OffsetMRect( &blobRect, 0, kBlobVertSize );
+		OffsetMRect( &shadowRect, 0, kBlobVertSize );
+
+		nextFrame = nextG[player]? kNoSuction: 
+						(nextM[player]? kFlashDarkBlob: jiggle[nextStage[player][1]]);
+		
+		SurfaceDrawShadow( &shadowRect, nextA[player], nextFrame );
+
+		CalcBlobRect( nextFrame, nextA[player]-1, &srcRect );
+		SurfaceBlitBlob( &srcRect, &blobRect );	  
+		
+		SDLU_ReleaseSurface( nextDrawSurface );
+
+		SDLU_BlitFrontSurface( nextDrawSurface, 
+		                       SDLU_MRectToSDLRect( &nextWindowZRect, &sourceSDLRect ),
+		                       SDLU_MRectToSDLRect( &nextWindowRect[player], &destSDLRect ) );
+	}
+}
--- /dev/null
+++ b/Source/next.h
@@ -1,0 +1,18 @@
+// next.h
+
+
+#include "SDL.h"
+
+
+void InitNext( void );
+void ShowNext( int player );
+void RefreshNext( int player );
+void UpdateNext( int player );
+void PullNext( int player );
+void ShowPull( int player );
+
+extern SDL_Surface* nextSurface;
+extern SDL_Surface* nextDrawSurface;
+
+extern MBoolean nextWindowVisible[2];
+extern MRect nextWindowZRect, nextWindowRect[2];
--- /dev/null
+++ b/Source/opponent.cpp
@@ -1,0 +1,257 @@
+// opponent.c
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "main.h"
+#include "level.h"
+#include "opponent.h"
+#include "gworld.h"
+#include "graphics.h"
+#include "random.h"
+#include "control.h"
+#include "players.h"
+#include "gameticks.h"
+#include "blitter.h"
+
+SDL_Surface* opponentSurface;
+SDL_Surface* opponentMaskSurface;
+SDL_Surface* opponentDrawSurface;
+
+MRect opponentWindowZRect, opponentWindowRect;
+int opponentMood, opponentFrame;
+int opponentTime, glowTime[kGlows], glowFrame[kGlows], panicTime, panicFrame;
+int heavyGlowArray[kGlowArraySize], glowArray[kGlowArraySize], lightGlowArray[kGlowArraySize];
+
+void InitOpponent( void )
+{
+	MRect    littleRect = {0, 0, 64, 64}, bigRect = {0, 0, 64, 64*(kOppFrames*3) };
+	SDL_Rect sdlRect;
+	double   index, value;
+	
+	opponentDrawSurface = SDLU_InitSurface( SDLU_MRectToSDLRect( &littleRect, &sdlRect ), 16 );
+	opponentSurface     = SDLU_InitSurface( SDLU_MRectToSDLRect( &bigRect, &sdlRect ), 16 );
+
+	bigRect.bottom *= kGlows + 1;
+	opponentMaskSurface = SDLU_InitSurface( SDLU_MRectToSDLRect( &bigRect, &sdlRect ), 1 );
+	
+	opponentWindowZRect.top = opponentWindowZRect.left = 0;
+	opponentWindowZRect.bottom = opponentWindowZRect.right = 64;
+	opponentWindowRect = opponentWindowZRect;
+	CenterRectOnScreen( &opponentWindowRect, 0.5, 0.5 );
+		
+	opponentMood = 0;
+	
+	for( index=0; index<kGlowArraySize; index++ )
+	{
+		value = sin( index*pi/kGlowArraySize );
+		value *= value;
+		
+		heavyGlowArray[(int)index] = (int)(value * 24.0);
+		glowArray     [(int)index] = (int)(value * 16.0);
+		lightGlowArray[(int)index] = (int)(value * 12.0);
+	}
+}
+
+void BeginOpponent( int which )
+{
+	int count;
+
+	DrawPICTInSurface( opponentSurface,     5000 + which );
+	DrawPICTInSurface( opponentMaskSurface, 5100 + which );
+
+	opponentTime = panicTime = GameTickCount( );
+	for( count=0; count<kGlows; count++ )
+	{
+		glowTime[count] = panicTime;
+		glowFrame[count] = 0;
+	}
+	
+	opponentMood = 0;
+	emotions[0] = emotions[1] = kEmotionNeutral;
+}
+
+void DrawFrozenOpponent( void )
+{
+	SDL_Rect   sourceSDLRect, destSDLRect;
+	MRect      myRect = {0, 0, 64, 64};
+
+	OffsetMRect( &myRect, opponentFrame * 64, 0 );
+
+	SDLU_BlitFrontSurface( opponentSurface, 
+	                       SDLU_MRectToSDLRect( &myRect, &sourceSDLRect ),
+	                       SDLU_MRectToSDLRect( &opponentWindowRect, &destSDLRect ) );
+}
+
+void OpponentPissed( void )
+{
+	opponentMood = 7;
+	opponentTime = GameTickCount();
+}
+
+void OpponentChatter( MBoolean on )
+{
+	switch( on )
+	{
+		case true:
+			opponentMood = 5;
+			opponentTime = GameTickCount();
+			break;
+			
+		case false:
+			opponentMood = 0;
+			opponentTime = GameTickCount();
+			break;
+	}
+}
+
+void UpdateOpponent( void )
+{
+	MRect    myRect = {0,0,64,64}, dstRect = {0,0,64,64}, maskRect;
+	int      emotiMap[] = {0, 1, 2, 1}, draw = false, count;
+	SDL_Rect srcSDLRect, dstSDLRect;
+	
+	if( GameTickCount( ) > opponentTime )
+	{
+		switch( opponentMood )
+		{
+			case 0: 				// Idle
+				opponentTime += 60 + RandomBefore(180);
+				opponentMood = RandomBefore(2) + 1;
+				opponentFrame = (emotiMap[emotions[1]] * kOppFrames);
+				break;
+			
+			case 1:					// Shifty Eyes
+				opponentTime += 40 + RandomBefore(60);
+				opponentMood = 0;
+				opponentFrame = (emotiMap[emotions[1]] * kOppFrames) + RandomBefore(2) + 1;
+				break;
+
+			case 2:					// Blinks
+				opponentTime += 3;
+				opponentMood = 3;
+				opponentFrame = (emotiMap[emotions[1]] * kOppFrames) + 3;
+				break;
+			
+			case 3:					// Blinks (more)
+				opponentTime += 3;
+				opponentMood = 4;
+				opponentFrame = (emotiMap[emotions[1]] * kOppFrames) + 4;
+				break;
+			
+			case 4: 				// Blinks (more)
+				opponentTime += 3;
+				opponentMood = 0;
+				opponentFrame = (emotiMap[emotions[1]] * kOppFrames) + 3;
+				break;
+			
+			case 5:                 // Chatter (only good for tutorial)
+				opponentTime += 8;
+				opponentMood = 6;
+				opponentFrame = 5;
+				break;
+
+			case 6:					// Chatter 2 (only good for tutorial)
+				opponentTime += 8;
+				opponentMood = 5;
+				opponentFrame = 6;
+				break;
+			
+			case 7:					// Pissed (when hit with punishments)
+				opponentTime += 60;
+				opponentFrame = 7;
+				opponentMood = 0;
+				break;
+		}
+		
+		draw = true;
+	}
+	
+	if( GameTickCount( ) > panicTime )
+	{
+		panicTime += 2;
+		
+		if( emotions[1] == kEmotionPanic )
+		{
+			if( ++panicFrame >= kGlowArraySize ) panicFrame = 0;
+			draw = true;
+		}
+		else
+		{
+			panicFrame = 0;
+		}
+	}
+	
+	for( count=0; count<kGlows; count++ )
+	{
+		if( GameTickCount( ) > glowTime[count] )
+		{
+			glowTime[count] += character[1].glow[count].time;
+			
+			if( character[1].glow[count].color )
+			{
+				if( ++glowFrame[count] >= kGlowArraySize ) glowFrame[count] = 0;
+				draw = true;
+			}
+			else
+			{
+				glowFrame[count] = 0;
+			}
+		}
+	}
+	
+	if( draw )
+	{
+		OffsetMRect( &myRect, 64*opponentFrame, 0 );
+		
+		SDLU_AcquireSurface( opponentDrawSurface );
+		
+		SDLU_BlitSurface( opponentSurface,     SDLU_MRectToSDLRect( &myRect, &srcSDLRect ),
+		                  opponentDrawSurface, SDLU_MRectToSDLRect( &dstRect, &dstSDLRect )  );
+		
+		maskRect = myRect;
+		for( count=0; count<kGlows; count++ )
+		{
+			OffsetMRect( &maskRect, 0, 64 );
+
+			if( glowFrame[count] )
+			{
+				if( character[1].glow[count].color & 0x8000 )
+				{
+					SurfaceBlitColor(  opponentMaskSurface,  opponentDrawSurface,
+					                  &maskRect,            &dstRect, 
+					                   (character[1].glow[count].color & 0x7C00) >> 10,
+									   (character[1].glow[count].color & 0x03E0) >> 5,
+									   (character[1].glow[count].color & 0x001F),
+									   heavyGlowArray[glowFrame[count]] );
+				}
+				else
+				{
+					SurfaceBlitColor(  opponentMaskSurface,  opponentDrawSurface,
+					                  &maskRect,            &dstRect, 
+					                   (character[1].glow[count].color & 0x7C00) >> 10,
+									   (character[1].glow[count].color & 0x03E0) >> 5,
+									   (character[1].glow[count].color & 0x001F),
+									   lightGlowArray[glowFrame[count]] );
+				}
+			}
+		}
+		
+		if( panicFrame )
+		{
+			SurfaceBlitColor(  opponentMaskSurface,  opponentDrawSurface,
+			                  &myRect,              &dstRect, 
+			                   31, 31, 22, glowArray[panicFrame] );
+		}
+		
+		SDLU_ReleaseSurface( opponentDrawSurface );
+		
+		SDLU_BlitFrontSurface( opponentDrawSurface, 
+		                       SDLU_MRectToSDLRect( &opponentWindowZRect, &srcSDLRect ),
+		                       SDLU_MRectToSDLRect( &opponentWindowRect,  &dstSDLRect )  );
+	}
+}
--- /dev/null
+++ b/Source/opponent.h
@@ -1,0 +1,26 @@
+// opponent.h
+
+void InitOpponent( void );
+void BeginOpponent( int which );
+void UpdateOpponent( void );
+void DrawFrozenOpponent( void );
+void OpponentChatter( MBoolean on );
+void OpponentPissed( void );
+
+enum
+{
+	kBored=0,
+	kLookRight,
+	kLookLeft,
+	kBlink1,
+	kBlink2
+};
+
+#define kOppFrames (kBlink2+1)
+#define kGlowArraySize 30
+
+extern int opponentMood, opponentFrame;
+extern int opponentTime, glowTime[kGlows], glowFrame[kGlows], panicTime, panicFrame;
+extern int glowArray[kGlowArraySize], lightGlowArray[kGlowArraySize];
+
+extern MRect opponentWindowZRect, opponentWindowRect;
--- /dev/null
+++ b/Source/pause.cpp
@@ -1,0 +1,1553 @@
+// pause.cpp
+
+// All of this code is fugly. I really needed a dialog manager, but I didn't know it at the time,
+// and instead I cobbled this together. It is just barely good enough to work. Fortunately it looks
+// decent to the end user...
+
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include <math.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "main.h"
+#include "gameticks.h"
+#include "blitter.h"
+#include "graphics.h"
+#include "gworld.h"
+#include "pause.h"
+#include "random.h"
+#include "font.h"
+#include "music.h"
+#include "soundfx.h"
+#include "keyselect.h"
+#include "level.h"
+#include "victory.h"
+#include "hiscore.h"
+#include "score.h"
+#include "RegAlgorithm.h"
+
+
+const char kEscapeKey = 0x1B;
+
+typedef struct
+{
+	float red, green, blue;
+}
+FRGBColor;
+
+SDL_Surface* backSurface;
+SDL_Surface* drawSurface;
+SDL_Surface* logoSurface;
+SDL_Surface* logoMaskSurface;
+SDL_Surface* logoAlphaSurface;
+
+SkittlesFontPtr smallFont, bigFont, dashedLineFont, continueFont, tinyFont, batsuFont;
+FRGBColor backColor[4];
+MBoolean continueTimeOut;
+
+static int dialogType, dialogStage, dialogTimer, dialogUndimTime, dialogTarget, dialogShade, dialogItem;
+static float colorWrap = 0, colorInc;
+static MRect logoRect = {0, 0, 111, 246}, lastPauseRect;
+static MBoolean dialogStageComplete;
+static MBoolean timeToRedraw = false;
+
+// for the controls dialog
+static int controlToReplace = -1;
+
+// for the enter code dialog
+static char  nameField[256], keyField[256];
+static char* whichField = nameField;
+static int   batsuAlpha = 0;
+
+static void ItsTimeToRedraw()
+{
+	timeToRedraw = true;
+}
+
+enum
+{
+	kTextRainbow,
+	kTextBrightRainbow,
+	kTextWhite,
+	kTextBlueGlow,
+	kTextGray,
+	kTextAlmostWhite
+};
+
+static MPoint DrawRainbowText( SkittlesFontPtr font, const char *line, MPoint dPoint, float wave, int bright )
+{
+	int   length, current;
+	int   r,g,b;
+	float s;
+	
+	current = 0;
+	length = strlen(line);
+	
+	switch( bright )
+	{	
+			case kTextGray:
+				r = g = b = 12;
+				break;
+				
+			case kTextBlueGlow:
+				s = sin(wave);
+				r = (int)(11.0 + 15.0 * s * s);
+				g = r;
+				b = 31;
+				break;
+				
+			case kTextWhite:
+				r = g = b = 31;
+				break;
+				
+			case kTextAlmostWhite:
+				r = g = b = 28;
+				break;
+				
+	}
+
+	while( line[current] )
+	{
+		switch( bright )
+		{
+			case kTextBrightRainbow:
+				r = (int)(26.0 + 5.0 * sin(wave                    ));
+				g = (int)(26.0 + 5.0 * sin(wave + ((2.*pi) * 1./3.)));
+				b = (int)(26.0 + 5.0 * sin(wave + ((2.*pi) * 2./3.)));
+				break;
+
+			case kTextRainbow:
+				r = (int)(16.0 + 12.0 * sin(wave                    ));
+				g = (int)(16.0 + 12.0 * sin(wave + ((2.*pi) * 1./3.)));
+				b = (int)(16.0 + 12.0 * sin(wave + ((2.*pi) * 2./3.)));
+				break;
+		}
+
+		SurfaceBlitCharacter( font, line[current], &dPoint, r, g, b, 1 );			
+		
+		wave += 0.2;
+		current++;
+	}
+	
+	return dPoint;
+}
+
+
+#define kEdgeSize 8
+static short edge[4][kEdgeSize][kEdgeSize];
+
+void SurfaceGetEdges( SDL_Surface* edgeSurface, const MRect *rect )
+{
+	unsigned char* src[4];
+	int            srcRowBytes, width, height, count;
+	
+	src[0] = src[1] = src[2] = src[3] = (unsigned char*) edgeSurface->pixels;
+	srcRowBytes = edgeSurface->pitch;
+
+	width  = rect->right  - rect->left;
+	height = rect->bottom - rect->top; 
+
+	src[0] += (srcRowBytes * (rect->top               )) + ((rect->left             ) * 2);
+	src[1] += (srcRowBytes * (rect->top               )) + ((rect->right - kEdgeSize) * 2);
+	src[2] += (srcRowBytes * (rect->bottom - kEdgeSize)) + ((rect->left             ) * 2);
+	src[3] += (srcRowBytes * (rect->bottom - kEdgeSize)) + ((rect->right - kEdgeSize) * 2);
+	
+	for( count=0; count<4; count++ )
+	{
+		for( height=0; height<kEdgeSize; height++ )
+		{
+			memcpy( edge[count][height], src[count], kEdgeSize * 2 );
+			src[count] += srcRowBytes;
+		}
+	}
+}
+
+
+void SurfaceCurveEdges( SDL_Surface* edgeSurface, const MRect *rect )
+{
+	unsigned char* src[4];
+	int srcRowBytes, width, height, count;
+	char edgeMap[4][kEdgeSize][kEdgeSize+1]={  "      --",
+					                           "    -...",
+					                           "   -.xxX",
+					                           "  -.xXXX",
+					                           " -.xXXXX",
+					                           " .xXXXXX",
+					                           "-.xXXXXX",
+					                           "-.XXXXXX",
+					                           "--      ",
+					                           "...-    ",
+					                           "Xxx.-   ",
+					                           "XXXx.-  ",
+					                           "XXXXx.- ",
+					                           "XXXXXx. ",
+					                           "XXXXXx.-",
+					                           "XXXXXX.-",
+					                           "-.XXXXXX",
+					                           "-.xXXXXX",
+					                           " .xXXXXX",
+					                           " -.xXXXX",
+					                           "  -.xXXX",
+					                           "   -.xxX",
+					                           "    -...",
+					                           "      --",
+					                           "XXXXXX.-",
+					                           "XXXXXx.-",
+					                           "XXXXXx. ",
+					                           "XXXXx.- ",
+					                           "XXXx.-  ",
+					                           "Xxx.-   ",
+					                           "...-    ",
+					                           "--      "  };
+	                         	                         
+	
+	src[0] = src[1] = src[2] = src[3] = (unsigned char*) edgeSurface->pixels;
+	srcRowBytes = edgeSurface->pitch;
+
+	src[0] += (srcRowBytes * (rect->top               )) + ((rect->left             ) * 2);
+	src[1] += (srcRowBytes * (rect->top               )) + ((rect->right - kEdgeSize) * 2);
+	src[2] += (srcRowBytes * (rect->bottom - kEdgeSize)) + ((rect->left             ) * 2);
+	src[3] += (srcRowBytes * (rect->bottom - kEdgeSize)) + ((rect->right - kEdgeSize) * 2);
+	
+	// Draw top/bottom border
+	{
+		short *srcT1 = (short*) (src[0]) + kEdgeSize;
+		short *srcB1 = (short*) (src[2] + (srcRowBytes*(kEdgeSize-1))) + kEdgeSize;
+		short *srcT2 = srcT1 + (srcRowBytes/2);
+		short *srcB2 = srcB1 - (srcRowBytes/2);
+		
+		for( width = rect->right - rect->left - (kEdgeSize * 2); width > 0; width-- )
+		{
+			*srcT1 = 0; srcT1++;
+			*srcB1 = 0; srcB1++;
+			*srcT2 = (*srcT2 >> 1) & 0x3DEF; srcT2++;
+			*srcB2 = (*srcB2 >> 1) & 0x3DEF; srcB2++;
+		}
+	}
+	
+	// Draw left/right border
+	{
+		unsigned char *srcL1 = (src[0] + (srcRowBytes * kEdgeSize));
+		unsigned char *srcR1 = (src[1] + (srcRowBytes * kEdgeSize)) + 2*(kEdgeSize-1);
+
+		unsigned char *srcL2 = srcL1 + 2;
+		unsigned char *srcR2 = srcR1 - 2;
+		
+		for( height = rect->bottom - rect->top - (kEdgeSize * 2); height > 0; height-- )
+		{
+			*(short*)srcL1 = 0; 
+			*(short*)srcR1 = 0;
+			*(short*)srcL2 = (*(short*)srcL2 >> 1) & 0x3DEF; 
+			*(short*)srcR2 = (*(short*)srcR2 >> 1) & 0x3DEF; 
+			
+			srcL1 += srcRowBytes; 
+			srcR1 += srcRowBytes;
+			srcL2 += srcRowBytes; 
+			srcR2 += srcRowBytes;
+		}
+	}
+		
+	// Draw curved edges
+	for( count=0; count<4; count++ )
+	{
+		short *srcS = (short*) src[count];
+		
+		for( height=0; height<kEdgeSize; height++ )
+		{
+			for( width=0; width<kEdgeSize; width++ )
+			{
+				switch( edgeMap[count][height][width] )
+				{
+					case ' ': 	*srcS = edge[count][height][width]; break;
+					case '.': 	*srcS = 0; break;
+					case 'x': 	*srcS = (*srcS >> 1) & 0x3DEF; break;
+					case '-':	*srcS = (edge[count][height][width] >> 1) & 0x3DEF; break;
+					case 'X': 	break;
+				}
+				srcS++;
+			}
+			srcS += (srcRowBytes / 2) - kEdgeSize;
+		}
+	}
+}
+
+static MBoolean SharewareNoticeIsStillWaiting()
+{
+	return MTickCount() < dialogUndimTime;
+}
+
+
+#define min(x,y) (((x)<(y))?(x):(y))
+#define max(x,y) (((x)>(y))?(x):(y))
+#define arrsize(x) (sizeof(x)/sizeof(x[0]))
+
+enum
+{
+	kOpening = 0, 
+	kClosing
+};
+
+static MBoolean DrawDialogBox( MBoolean larger, int animationType, int *target, int skip, float *colorWrap, float colorInc, MRect *pauseRect )
+{
+	MBoolean animationStageComplete = false;
+	MRect normalRect[2][19]     = { { { 240 - 10,  320 - 30,  240 + 10,  320 + 30  },
+	                                  { 240 - 40,  320 - 120, 240 + 40,  320 + 120 },
+	                                  { 240 - 60,  320 - 180, 240 + 60,  320 + 180 },
+	                                  { 240 - 70,  320 - 210, 240 + 70,  320 + 210 },
+	                                  { 240 - 80,  320 - 230, 240 + 80,  320 + 230 },
+	                                  { 240 - 88,  320 - 245, 240 + 88,  320 + 245 },
+	                                  { 240 - 95,  320 - 252, 240 + 95,  320 + 252 },
+	                                  { 240 - 101, 320 - 255, 240 + 101, 320 + 255 },
+	                                  { 240 - 106, 320 - 252, 240 + 106, 320 + 252 },
+	                                  { 240 - 110, 320 - 245, 240 + 110, 320 + 245 },
+	                                  { 240 - 113, 320 - 238, 240 + 113, 320 + 238 },
+	                                  { 240 - 115, 320 - 232, 240 + 115, 320 + 232 },
+	                                  { 240 - 116, 320 - 228, 240 + 116, 320 + 228 },
+	                                  { 240 - 118, 320 - 232, 240 + 118, 320 + 230 },
+	                                  { 240 - 118, 320 - 238, 240 + 118, 320 + 232 },
+	                                  { 240 - 119, 320 - 242, 240 + 119, 320 + 242 },
+	                                  { 240 - 119, 320 - 244, 240 + 119, 320 + 244 },
+	                                  { 240 - 119, 320 - 242, 240 + 119, 320 + 242 },
+	                                  { 240 - 120, 320 - 240, 240 + 120, 320 + 240 }  },
+	                                { { 240 - 110, 320 - 220, 240 + 110, 320 + 220 }, 
+	                                  { 240 - 105, 320 - 210, 240 + 105, 320 + 210 }, 
+	                                  { 240 - 100, 320 - 200, 240 + 100, 320 + 200 }, 
+	                                  { 240 - 95,  320 - 190, 240 + 95,  320 + 190 }, 
+	                                  { 240 - 90,  320 - 180, 240 + 90,  320 + 180 }, 
+	                                  { 240 - 85,  320 - 170, 240 + 85,  320 + 170 }, 
+	                                  { 240 - 80,  320 - 160, 240 + 80,  320 + 160 }, 
+	                                  { 240 - 75,  320 - 150, 240 + 75,  320 + 150 }, 
+	                                  { 240 - 70,  320 - 140, 240 + 70,  320 + 140 }, 
+	                                  { 240 - 65,  320 - 130, 240 + 65,  320 + 130 }, 
+	                                  { 240 - 60,  320 - 120, 240 + 60,  320 + 120 }, 
+	                                  { 240 - 55,  320 - 110, 240 + 55,  320 + 110 }, 
+	                                  { 240 - 50,  320 - 100, 240 + 50,  320 + 100 }, 
+	                                  { 240 - 45,  320 - 90,  240 + 45,  320 + 90  }, 
+	                                  { 240 - 40,  320 - 80,  240 + 40,  320 + 80  }, 
+	                                  { 240 - 35,  320 - 70,  240 + 35,  320 + 70  }, 
+	                                  { 240 - 30,  320 - 60,  240 + 30,  320 + 60  }, 
+	                                  { 240 - 25,  320 - 50,  240 + 25,  320 + 50  }, 	                                
+	                                  { 240 - 20,  320 - 40,  240 + 20,  320 + 40  }  }
+	                              };
+
+	MRect largerRect[2][19]     = { { { 240 - 11,  320 - 30,  240 + 11,  320 + 30  },
+	                                  { 240 - 44,  320 - 120, 240 + 44,  320 + 120 },
+	                                  { 240 - 66,  320 - 180, 240 + 66,  320 + 180 },
+	                                  { 240 - 77,  320 - 210, 240 + 77,  320 + 210 },
+	                                  { 240 - 88,  320 - 230, 240 + 88,  320 + 230 },
+	                                  { 240 - 97,  320 - 245, 240 + 97,  320 + 245 },
+	                                  { 240 - 104, 320 - 252, 240 + 104, 320 + 252 },
+	                                  { 240 - 111, 320 - 255, 240 + 111, 320 + 255 },
+	                                  { 240 - 117, 320 - 252, 240 + 117, 320 + 252 },
+	                                  { 240 - 121, 320 - 245, 240 + 121, 320 + 245 },
+	                                  { 240 - 124, 320 - 238, 240 + 124, 320 + 238 },
+	                                  { 240 - 126, 320 - 232, 240 + 126, 320 + 232 },
+	                                  { 240 - 128, 320 - 228, 240 + 128, 320 + 228 },
+	                                  { 240 - 130, 320 - 232, 240 + 130, 320 + 230 },
+	                                  { 240 - 130, 320 - 238, 240 + 130, 320 + 232 },
+	                                  { 240 - 131, 320 - 242, 240 + 131, 320 + 242 },
+	                                  { 240 - 131, 320 - 244, 240 + 131, 320 + 244 },
+	                                  { 240 - 131, 320 - 242, 240 + 131, 320 + 242 },
+	                                  { 240 - 132, 320 - 240, 240 + 132, 320 + 240 }  },
+	                                { { 240 - 121, 320 - 220, 240 + 121, 320 + 220 }, 
+	                                  { 240 - 115, 320 - 210, 240 + 115, 320 + 210 }, 
+	                                  { 240 - 110, 320 - 200, 240 + 110, 320 + 200 }, 
+	                                  { 240 - 104, 320 - 190, 240 + 104, 320 + 190 }, 
+	                                  { 240 - 99,  320 - 180, 240 + 99,  320 + 180 }, 
+	                                  { 240 - 93,  320 - 170, 240 + 93,  320 + 170 }, 
+	                                  { 240 - 88,  320 - 160, 240 + 88,  320 + 160 }, 
+	                                  { 240 - 82,  320 - 150, 240 + 82,  320 + 150 }, 
+	                                  { 240 - 77,  320 - 140, 240 + 77,  320 + 140 }, 
+	                                  { 240 - 71,  320 - 130, 240 + 71,  320 + 130 }, 
+	                                  { 240 - 66,  320 - 120, 240 + 66,  320 + 120 }, 
+	                                  { 240 - 60,  320 - 110, 240 + 60,  320 + 110 }, 
+	                                  { 240 - 55,  320 - 100, 240 + 55,  320 + 100 }, 
+	                                  { 240 - 49,  320 - 90,  240 + 49,  320 + 90  }, 
+	                                  { 240 - 44,  320 - 80,  240 + 44,  320 + 80  }, 
+	                                  { 240 - 38,  320 - 70,  240 + 38,  320 + 70  }, 
+	                                  { 240 - 33,  320 - 60,  240 + 33,  320 + 60  }, 
+	                                  { 240 - 27,  320 - 50,  240 + 27,  320 + 50  }, 	                                
+	                                  { 240 - 22,  320 - 40,  240 + 22,  320 + 40  }  }
+	                              };
+
+	int      colorInt, shading;
+	float    colorFrac, nColorFrac;
+	MRect    newRect;
+	SDL_Rect sdlRect;
+	
+	if( *target > 18 )
+	{
+		*target = 18;
+		animationStageComplete = true;
+	}
+
+	colorInt  = (int) floor( *colorWrap );
+	colorFrac = *colorWrap - colorInt;
+
+	newRect = larger? largerRect[animationType][*target]: normalRect[animationType][*target];
+	shading = ((animationType == 0) ? (*target * 24 / 18): (24 - (*target * 2 / 3)));
+	
+	{
+		float r1 = backColor[colorInt      ].red, g1 = backColor[colorInt      ].green, b1 = backColor[colorInt      ].blue,
+		      r2 = backColor[(colorInt+1)&3].red, g2 = backColor[(colorInt+1)&3].green, b2 = backColor[(colorInt+1)&3].blue,
+		      r3 = backColor[(colorInt+2)&3].red, g3 = backColor[(colorInt+2)&3].green, b3 = backColor[(colorInt+2)&3].blue,
+		      r4 = backColor[(colorInt+3)&3].red, g4 = backColor[(colorInt+3)&3].green, b4 = backColor[(colorInt+3)&3].blue;
+		
+		nColorFrac = 1 - colorFrac;
+		
+		SDLU_AcquireSurface( drawSurface );
+		
+		SurfaceBlitBlendOver(  backSurface,  drawSurface, 
+		                      &newRect,     &newRect,
+  		                       (int)((r1 * nColorFrac) + (r2 * colorFrac)), 
+						       (int)((g1 * nColorFrac) + (g2 * colorFrac)), 
+						       (int)((b1 * nColorFrac) + (b2 * colorFrac)), 
+						       (int)((r2 * nColorFrac) + (r3 * colorFrac)), 
+						       (int)((g2 * nColorFrac) + (g3 * colorFrac)), 
+						       (int)((b2 * nColorFrac) + (b3 * colorFrac)), 
+						       (int)((r4 * nColorFrac) + (r1 * colorFrac)), 
+						       (int)((g4 * nColorFrac) + (g1 * colorFrac)), 
+						       (int)((b4 * nColorFrac) + (b1 * colorFrac)), 
+						       (int)((r3 * nColorFrac) + (r4 * colorFrac)), 
+						       (int)((g3 * nColorFrac) + (g4 * colorFrac)), 
+						       (int)((b3 * nColorFrac) + (b4 * colorFrac)), 
+						       shading );
+
+		if( pauseRect->left < newRect.left ) 
+		{
+			MRect eraseRect = *pauseRect;
+			pauseRect->left = eraseRect.right = newRect.left;
+			
+			SDLU_MRectToSDLRect( &eraseRect, &sdlRect );
+			SDLU_BlitSurface( backSurface, &sdlRect,
+			                  drawSurface, &sdlRect  );
+		}
+
+		if( pauseRect->right > newRect.right ) 
+		{
+			MRect eraseRect = *pauseRect;
+			pauseRect->right = eraseRect.left = newRect.right;
+			
+			SDLU_MRectToSDLRect( &eraseRect, &sdlRect );
+			SDLU_BlitSurface( backSurface, &sdlRect,
+			                  drawSurface, &sdlRect  );
+		}
+
+		if( pauseRect->top < newRect.top ) 
+		{
+			MRect eraseRect = *pauseRect;
+			pauseRect->top = eraseRect.bottom = newRect.top;
+
+			SDLU_MRectToSDLRect( &eraseRect, &sdlRect );
+			SDLU_BlitSurface( backSurface, &sdlRect,
+			                  drawSurface, &sdlRect  );
+		}
+
+		if( pauseRect->bottom > newRect.bottom ) 
+		{
+			MRect eraseRect = *pauseRect;
+			pauseRect->bottom = eraseRect.top = newRect.bottom;
+
+			SDLU_MRectToSDLRect( &eraseRect, &sdlRect );
+			SDLU_BlitSurface( backSurface, &sdlRect,
+			                  drawSurface, &sdlRect  );
+		}
+
+		SDLU_ReleaseSurface( drawSurface );
+	}
+	
+	*pauseRect = newRect;
+	
+	*colorWrap += colorInc * skip;
+	if( *colorWrap >= 4 ) *colorWrap -= 4;
+
+	*target += skip;
+
+	return animationStageComplete;
+}
+
+static void DrawDialogCursor( MRect *pauseRect, int *shade )
+{
+	MPoint p, q;
+    shade;
+    
+	SDLU_GetMouse( &p );
+	
+	if( p.h < (pauseRect->left      ) ) p.h = pauseRect->left;
+	if( p.h > (pauseRect->right  - 5) ) p.h = pauseRect->right  - 5;
+	if( p.v < (pauseRect->top       ) ) p.v = pauseRect->top;
+	if( p.v > (pauseRect->bottom - 5) ) p.v = pauseRect->bottom - 5;
+	q = p;
+	
+	SDLU_AcquireSurface( drawSurface );
+
+	SurfaceBlitCharacter( smallFont, '�', &p,  0,  0,  0, 0 );			
+	SurfaceBlitCharacter( smallFont, '�', &q, 31, 31, 31, 0 );			
+	
+	SDLU_ReleaseSurface( drawSurface );
+}
+
+static void DrawDialogLogo( MRect *pauseRect, int shade )
+{
+	MRect drawRect;
+	int alpha;
+
+	drawRect.left   = (pauseRect->left + ((pauseRect->right - pauseRect->left) * 1 / 2) ) - (logoRect.right / 2);
+	drawRect.top    = (pauseRect->top + 14);
+	drawRect.bottom = drawRect.top + logoRect.bottom;
+	drawRect.right  = drawRect.left + logoRect.right;
+	
+	SDLU_AcquireSurface( drawSurface );
+	
+	alpha = (shade > 63)? 31: (shade / 2);
+		
+	SurfaceBlitWeightedDualAlpha(  drawSurface,  logoSurface,  logoMaskSurface,  logoAlphaSurface,  drawSurface,
+                                  &drawRect,    &logoRect,    &logoRect,        &logoRect,         &drawRect,
+                                   alpha );
+
+	SDLU_ReleaseSurface( drawSurface );
+}
+
+
+enum
+{ 
+	kNothing = -1,
+	
+// main pause screen (kEndGame is reused in continue and register)
+	kMusic = 0,		kEndGame,
+	kSound,			kPauseGame,
+	kControls,		kResume,
+	kRegisterNow,   kSecret,
+	kWarp,       	kSoundTest,
+
+// continue screen
+    kContinue,      
+    
+// register screen
+    kLater,
+    
+// controls screen
+    k1PLeft,        k2PLeft,
+    k1PRight,       k2PRight,
+    k1PDrop,        k2PDrop,
+    k1PRotate,      k2PRotate,
+    kControlsOK,    kControlsReset,
+   
+// shareware notice screen
+	kSharewareNoticeNotYet, 
+	kSharewareNoticeEnterCode,
+	kSharewareNoticePurchase,
+	
+// enter code screen
+	kEnterCodeOK,
+	kEnterCodeNotYet
+};
+
+static void DrawContinueContents( int *item, int shade )
+{
+	char line[4][50] = { "Do you want to continue?",
+	                     "Yes",
+	                     "No",
+	                     "" };	                 
+	MPoint dPoint[4] = { {233, 210}, {280, 220}, {280, 400}, {335, 400} }, hPoint = {255, 320};
+	static int lastCountdown = 0;
+	int index, countdown, fade;
+	int r, g, b;
+	                 
+	sprintf( line[3], "%d credit%c", credits, (credits != 1)? 's': ' ' );
+
+	SDLU_AcquireSurface( drawSurface );
+
+	for( index=0; index<4; index++ )
+	{	
+		DrawRainbowText( smallFont, line[index], dPoint[index], (0.25 * index) + (0.075 * shade), 
+						 ( (index == 0)                          ||
+						  ((index == 1) && (*item == kContinue)) ||
+						  ((index == 2) && (*item == kEndGame ))    )? kTextBrightRainbow: kTextRainbow );
+	}
+	
+	countdown = shade / 100;
+	if( countdown < 10 )
+	{
+		continueTimeOut = false;
+		
+		if( (countdown != 0) && (countdown != lastCountdown) )
+		{
+			PlayMono( kContinueSnd );
+		}
+		lastCountdown = countdown;
+		
+		if( countdown < 5 )
+		{
+			r = (countdown * 31) / 5;
+			g = 31;
+		}
+		else
+		{
+			r = 31;
+			g = ((10 - countdown) * 31) / 5;
+		}
+			
+		fade = shade % 100;
+		if( fade > 50 ) fade = 50;
+		r = ((31 * (49 - fade)) + (r * fade)) / 49;
+		g = ((31 * (49 - fade)) + (g * fade)) / 49;
+		b = ((31 * (49 - fade))) / 49;
+		
+		countdown = '9' - countdown;
+		hPoint.h -= continueFont->width[countdown] / 2;
+
+		for( shade = 4; shade > 0; shade-- )
+		{
+			MPoint hP = hPoint;
+			
+			hP.h += 2 * shade;
+			hP.v += 2 * shade;
+
+			SurfaceBlitWeightedCharacter( continueFont, countdown, &hP, 0, 0, 0, 20 - 4*shade ); 
+		}
+
+		SurfaceBlitCharacter( continueFont, countdown, &hPoint, r, g, b, 0 ); 
+	}
+	else
+	{
+		continueTimeOut = true;
+	}
+	
+	SDLU_ReleaseSurface( drawSurface );
+}
+
+static void DrawHiScoreContents( int *item, int shade )
+{
+	MPoint dPoint[3] = { {240, 640}, {260, 640}, {335, 400} }, hPoint = {294, 145};
+	MPoint dashedLinePoint = { 320, 140 };
+	int    index;		
+	int    nameLength;
+	char  *line[3], *scan;
+	
+	item; // is unused
+
+	line[0] = highScoreText;
+	line[1] = "Please enter your name and press return:";
+	line[2] = highScoreRank;
+
+	for( index=0; index<2; index++ )
+	{
+		scan = line[index];
+		while( *scan )
+			dPoint[index].h -= smallFont->width[*scan++];
+		
+		dPoint[index].h /= 2;
+	}	
+		
+	SDLU_AcquireSurface( drawSurface );	
+
+	while( dashedLinePoint.h < 490 )
+	{
+		SurfaceBlitCharacter( dashedLineFont, '.', &dashedLinePoint, 0, 0, 0, 0 );
+	}
+	
+	nameLength = strlen(highScoreName);
+	for( index = 0; index < nameLength; index++ )
+	{
+		SurfaceBlitCharacter( bigFont, highScoreName[index], &hPoint, 31, 31, 31, 1 );			
+		if( hPoint.h >= 475 )
+		{
+			highScoreName[index] = '\0';
+			break;
+		}
+	}
+
+	index = (int)(( 1.0 + sin( MTickCount() / 7.5 ) ) * 15.0);	
+	SurfaceBlitCharacter( bigFont, '|', &hPoint, index, index, 31, 1 );			
+
+	for( index=0; index<3; index++ )
+	{	
+		DrawRainbowText( smallFont, line[index], dPoint[index], (0.25 * index) + (0.075 * shade), (index != 2)? kTextBrightRainbow: kTextRainbow );
+	}
+	
+	SDLU_ReleaseSurface( drawSurface );	
+}
+
+static void DrawRegisterContents( int *item, int shade )
+{
+	int index;		
+	MPoint dPoint[4] = { {240, 150}, {260, 160}, {305, 170}, {305, 400} };
+	char line[4][50] = {  "Sorry, you must register Candy Crisis",
+	                      "to gain access to two player mode!",
+	                      "Register Now",
+	                      "Not Yet" };
+	
+	SDLU_AcquireSurface( drawSurface );	
+
+	for( index=0; index<4; index++ )
+	{	
+		DrawRainbowText( smallFont, line[index], dPoint[index], (0.25 * index) + (0.075 * shade), 
+						 ( (index == 0)                              ||
+						   (index == 1)                              ||
+						  ((index == 2) && (*item == kRegisterNow )) ||
+						  ((index == 3) && (*item == kLater       ))    )? kTextBrightRainbow: kTextRainbow );
+	}
+
+	SDLU_ReleaseSurface( drawSurface );	
+}
+
+static void DrawControlsContents( int *item, int shade )
+{
+	MBoolean    highlight;
+	MPoint      dPoint;
+	int         index;
+	const char* controlName;
+	int         r, g, b;
+	const char  label[8][20] = { "1P Left",   "2P Left", 
+	                             "1P Right",  "2P Right", 
+	                             "1P Drop",   "2P Drop",
+	                             "1P Rotate", "2P Rotate" };
+	                           
+	                         
+	SDLU_AcquireSurface( drawSurface );	
+
+	for( index=0; index<8; index++ )
+	{	
+		highlight = (index == (*item - k1PLeft));
+		
+		dPoint.v = 229 + ((index & ~1) * 13);
+		dPoint.h = (index & 1)? 325: 130;
+		DrawRainbowText( smallFont, label[index], dPoint, (0.25 * index) + (0.075 * shade), highlight? kTextBrightRainbow: kTextRainbow );
+				
+		dPoint.v = 245 + ((index & ~1) * 13);
+		dPoint.h = (index & 1)? 420: 225;		
+		r = (int)(highlight? 31.0: 0.0);
+		g = b = (int)(highlight? 31.0 - (11.0 * (sin(shade * 0.2) + 1.0)): 0.0);
+		SurfaceBlitCharacter( dashedLineFont, '.', &dPoint, r, g, b, 0 ); 
+		SurfaceBlitCharacter( dashedLineFont, '.', &dPoint, r, g, b, 0 ); 
+		SurfaceBlitCharacter( dashedLineFont, '.', &dPoint, r, g, b, 0 ); 
+		SurfaceBlitCharacter( dashedLineFont, '.', &dPoint, r, g, b, 0 ); 
+		SurfaceBlitCharacter( dashedLineFont, '.', &dPoint, r, g, b, 0 );  // 80 pixels across
+		
+		controlName = SDL_GetKeyName( playerKeys[index & 1][index >> 1] );
+		if( controlName == NULL ) controlName = "???";
+		
+		dPoint.v = 231 + ((index & ~1) * 13);
+		dPoint.h = (index & 1)? 460: 265;		
+		dPoint.h -= GetTextWidth( tinyFont, controlName ) / 2;
+		DrawRainbowText( tinyFont, controlName, dPoint, (0.1 * shade), (controlToReplace == index)? kTextBlueGlow: kTextWhite );		
+	}
+
+	dPoint.h = 200;
+	dPoint.v = 340;
+	DrawRainbowText( smallFont, "� OK", dPoint, 8.0 + (0.075 * shade), (*item == kControlsOK)? kTextBrightRainbow: kTextRainbow );
+
+	dPoint.h = 365;
+	dPoint.v = 340;
+	DrawRainbowText( smallFont, "� Reset", dPoint, 8.25 + (0.075 * shade), (*item == kControlsReset)? kTextBrightRainbow: kTextRainbow );
+	
+	SDLU_ReleaseSurface( drawSurface );
+}
+
+static void DrawPauseContents( int *item, int shade )
+{
+	MPoint dPoint;
+	int itemCount = IsRegistered()? 6: 7;
+	int index;
+	char *line[7] = { "� Music",           "� End Game",
+	                  "� Sound",           "� Hide Game",
+	                  "� Controls",        "� Resume",
+	                  "� Register Now"                       };
+
+	
+	if( level == kTutorialLevel ) line[1] = "� Skip Tutorial";
+	
+	if( !musicOn ) line[0] = "� Music";
+	if( !soundOn ) line[2] = "� Sound";
+
+	SDLU_AcquireSurface( drawSurface );	
+	
+	for( index=0; index<itemCount; index++ )
+	{	
+		dPoint.h = (index & 1)? 340: 180;
+		dPoint.v = 240 + ((index & ~1) * 15);
+		
+		DrawRainbowText( smallFont, line[index], dPoint, (0.25 * index) + (0.075 * shade), (*item == index)? kTextBrightRainbow: kTextRainbow );
+	}
+	
+	SDLU_ReleaseSurface( drawSurface );
+}
+
+static void DrawSharewareNoticeContents( int *item, int shade )
+{
+	MPoint      dPoint;
+	int         index;
+	MBoolean    itemsAreDimmed;
+	const char* line[10] = { "Candy Crisis is not free! You have a 30 day trial",
+	                         "period to try out the software. After 30 days, if",
+	                         "you wish to continue playing Candy Crisis, you",
+	                         "are expected to register.",
+	                         "",
+	                         "When you register Candy Crisis, we will send a",
+	                         "unique registration code to you by e-mail or postal",
+	                         "mail. This registration code will allow you to play",
+	                         "all twelve levels of the game, enables two player",
+	                         "mode, and removes these shareware notices." };
+
+	itemsAreDimmed = SharewareNoticeIsStillWaiting();
+	
+	SDLU_AcquireSurface( drawSurface );	
+	
+	dPoint.v = 124;
+	dPoint.h = 140;
+	DrawRainbowText( bigFont, "Candy Crisis is shareware!", dPoint, 0, kTextWhite );
+	
+	for( index=0; index<10; index++ )
+	{	
+		dPoint.v = 165 + (16 * index);
+		dPoint.h = 156;
+		DrawRainbowText( tinyFont, line[index], dPoint, 0, 2 );
+	}
+	
+	dPoint.v = 340;
+	dPoint.h = 130;
+	DrawRainbowText( smallFont, "� Not Yet", dPoint, (shade * 0.1), itemsAreDimmed? kTextGray: (*item == kSharewareNoticeNotYet? kTextBlueGlow: kTextWhite) );
+
+	dPoint.v = 340;
+	dPoint.h = 260;
+	DrawRainbowText( smallFont, "� Purchase", dPoint, (shade * 0.1), itemsAreDimmed? kTextGray: (*item == kSharewareNoticePurchase? kTextBlueGlow: kTextWhite) );
+
+	dPoint.v = 340;
+	dPoint.h = 390;
+	DrawRainbowText( smallFont, "� Enter Code", dPoint, (shade * 0.1), itemsAreDimmed? kTextGray: (*item == kSharewareNoticeEnterCode? kTextBlueGlow: kTextWhite) );
+
+	SDLU_ReleaseSurface( drawSurface );
+}
+
+static void DrawEnterCodeContents( int *item, int shade )
+{
+	MPoint      dPoint;
+	int         index;
+	const char* line[4] = { "Thank you for your purchase! First, enter your",
+	                        "name below. Please make sure it matches your",
+	                        "name exactly as it appears in your registration",
+	                        "email." };
+	                         
+	SDLU_AcquireSurface( drawSurface );	
+	
+	dPoint.v = 124;
+	dPoint.h = 240;
+	DrawRainbowText( bigFont, "Enter Code", dPoint, 0, kTextWhite );
+	
+	for( index=0; index<4; index++ )
+	{	
+		dPoint.v = 165 + (16 * index);
+		dPoint.h = 156;
+		DrawRainbowText( tinyFont, line[index], dPoint, 0, kTextAlmostWhite );
+	}
+
+	dPoint.v = 165 + (int)(5.75 * 16);
+	dPoint.h = 140;
+	DrawRainbowText( dashedLineFont, "......................", dPoint, 0, kTextGray );
+
+	dPoint.v = 165 + (int)(4.5 * 16);
+	dPoint.h = 150;
+	dPoint = DrawRainbowText( smallFont, nameField, dPoint, 0, kTextWhite );
+	
+	if( whichField == nameField )
+	{
+		DrawRainbowText( smallFont, "|", dPoint, (shade * 0.1), kTextBlueGlow );
+	}
+	
+	dPoint.v = 165 + (int)(7 * 16);
+	dPoint.h = 156;
+	DrawRainbowText( tinyFont, "Next, enter your registration code.", dPoint, 0, kTextAlmostWhite );
+	
+
+	dPoint.v = 165 + (int)(9.75 * 16);
+	dPoint.h = 140;
+	DrawRainbowText( dashedLineFont, "......................", dPoint, 0, kTextGray );
+
+	dPoint.v = 165 + (int)(8.5 * 16);
+	dPoint.h = 150;
+	dPoint = DrawRainbowText( smallFont, keyField, dPoint, 0, kTextWhite );
+
+	if( whichField == keyField )
+	{
+		DrawRainbowText( smallFont, "|", dPoint, (shade * 0.1), kTextBlueGlow );
+	}
+	
+	dPoint.v = 340;
+	dPoint.h = 150;
+	DrawRainbowText( smallFont, "� OK", dPoint, (shade * 0.1), (*item == kEnterCodeOK? kTextBlueGlow: kTextWhite) );
+
+	dPoint.v = 340;
+	dPoint.h = 380;
+	DrawRainbowText( smallFont, "� Go Back", dPoint, (shade * 0.1), (*item == kEnterCodeNotYet? kTextBlueGlow: kTextWhite) );
+
+	if( batsuAlpha > 0 )
+	{
+		dPoint.v = 240 - 111;
+		dPoint.h = 320 - 111;
+		SurfaceBlitWeightedCharacter( batsuFont, 'X', &dPoint, 31, 0, 0, batsuAlpha-- );
+	}
+	
+	SDLU_ReleaseSurface( drawSurface );
+}
+
+static MBoolean ContinueSelected( int *item, unsigned char inKey, SDLKey inSDLKey )
+{
+	MRect yes = {280, 220, 300, 260}, no = {280, 400, 300, 440};
+	MPoint p;
+	
+	inSDLKey; // is unused 
+	
+	if( continueTimeOut )
+	{
+		*item = kEndGame;
+		return true;
+	}
+	
+	if( inKey == kEscapeKey )
+	{
+		*item = kContinue;
+		return true;
+	}
+	
+	SDLU_GetMouse( &p );
+
+	     if( MPointInMRect( p, &yes ) ) *item = kContinue;	
+	else if( MPointInMRect( p, &no  ) ) *item = kEndGame;
+	else *item = kNothing;
+	
+	return( SDLU_Button( ) && (*item != kNothing) );
+}
+
+static MBoolean RegisterSelected( int *item, unsigned char inKey, SDLKey inSDLKey )
+{
+	MRect registerNow = {305, 170, 325, 290}, registerLater = {305, 400, 325, 470};
+	MPoint p;
+	
+	inKey, inSDLKey; // is unused 
+		
+	if( inKey == kEscapeKey )
+	{
+		*item = kLater;
+		return true;
+	}
+	
+	SDLU_GetMouse( &p );
+
+	     if( MPointInMRect( p, &registerNow   ) ) *item = kRegisterNow;	
+	else if( MPointInMRect( p, &registerLater ) ) *item = kLater;
+	else *item = kNothing;
+	
+	if( SDLU_Button( ) )
+	{
+		switch( *item ) 
+		{
+			case kRegisterNow:
+				PlayMono( kClick );
+				return true;
+			
+			case kLater:
+				PlayMono( kClick );
+				return true;
+		}
+	}
+	
+	return false;
+}
+
+static MBoolean HiScoreSelected( int *item, unsigned char inKey, SDLKey inSDLKey )
+{
+	int nameLength;
+	
+	inSDLKey; // is unused
+	
+	nameLength = strlen(highScoreName);
+	
+	// return
+	if( inKey == 13 ) 
+	{
+		if( nameLength > 0 )
+		{
+			*item = kResume;
+			PlayMono( kSquishy );
+			return true;
+		}
+		else
+		{
+			PlayMono( kClick );
+		}
+	}
+	
+	// backspace
+	else if( inKey == 8 )
+	{
+		if( nameLength > 0 )
+		{
+			highScoreName[ nameLength-1 ] = '\0';
+			PlayMono( kClick );
+		}
+	}
+	
+	// characters
+	else if( bigFont->width[inKey] != 0 )
+	{
+		highScoreName[ nameLength++ ] = inKey;
+		highScoreName[ nameLength   ] = '\0';
+		PlayMono( kPlace );
+	}
+	
+	*item = kNothing;
+	return false;
+}
+
+
+static MBoolean ControlsSelected( int *item, unsigned char inKey, SDLKey inSDLKey )
+{
+	MPoint          p;
+	MRect           dRect;
+	int             index;
+	static MBoolean lastDown = false;
+	MBoolean        down;
+	MRect           okRect = { 340, 200, 360, 255 };
+	MRect           resetRect = { 340, 365, 360, 450 };
+	int             returnValue = 0;
+	
+	inKey; // unused
+	
+	*item = kNothing;
+
+	down = SDLU_Button();
+	SDLU_GetMouse( &p );
+
+	if( MPointInMRect( p, &okRect ) )
+	{
+		*item = kControlsOK;
+		if( down )
+		{
+			PlayMono( kClick );
+			returnValue = 1;
+			controlToReplace = -1;
+		}
+	}
+	else if( MPointInMRect( p, &resetRect ) )
+	{
+		*item = kControlsReset;
+		if( down && !lastDown )
+		{
+			PlayMono( kClick );
+			memcpy( playerKeys, defaultPlayerKeys, sizeof(playerKeys) );
+		}
+	}
+	else
+	{
+		for( index=0; index<8; index++ )
+		{
+			dRect.top    = 229 + ((index & ~1) * 13);
+			dRect.left   = (index & 1)? 325: 130;
+			dRect.bottom = dRect.top + 24;
+			dRect.right  = dRect.left + 175;
+
+			if( MPointInMRect( p, &dRect ) )
+			{
+				*item = k1PLeft + index;
+				if( down && !lastDown && !AnyKeyIsPressed() ) 
+				{
+					controlToReplace = (controlToReplace == index)? -1: index;
+				}
+				break;
+			}
+		}
+	}
+	
+	if( inSDLKey != 0 && controlToReplace != -1 )
+	{
+		playerKeys[controlToReplace & 1][controlToReplace >> 1] = inSDLKey;
+		controlToReplace = -1;
+	}
+	
+	lastDown = down;
+	
+	return returnValue;
+}
+
+
+static MBoolean PauseSelected( int *item, unsigned char inKey, SDLKey inSDLKey )
+{
+	inSDLKey; // is unused
+	
+	MRect targetRect[] = 
+	{	
+		{ 240, 180, 260, 320 },
+		{ 240, 340, 260, 480 },
+		{ 270, 180, 290, 320 },
+		{ 270, 340, 290, 480 },
+		{ 300, 180, 320, 320 },
+		{ 300, 340, 320, 480 },
+		{ 330, 180, 350, 320 },
+	    { 120, 550, 130, 560 }
+	};
+
+	static MBoolean lastDown = false;
+	int trigger;
+	int index;
+	MPoint p;
+	
+	SDLU_GetMouse( &p );
+	
+	trigger = SDLU_Button();
+	if( inKey == kEscapeKey )
+	{
+		*item = kResume;
+		trigger = true;
+	}
+	else
+	{
+		*item = kNothing;
+		for( index=0; index<arrsize(targetRect); index++ )
+		{
+			if( MPointInMRect( p, &targetRect[index] ) )
+			{
+				*item = index;
+			}
+		}
+	}
+	
+	if( trigger )
+	{
+		if( !lastDown )
+		{
+			lastDown = true;
+			
+			switch( *item )
+			{
+				case kSound:     PlayMono( kClick ); soundOn = !soundOn; PlayMono( kClick );     return false;
+				case kMusic:     PlayMono( kClick ); musicOn = !musicOn; EnableMusic( musicOn ); return false;
+				case kEndGame:   PlayMono( kClick );                                             return true;
+				case kResume:    PlayMono( kClick );                                             return true;
+
+				case kPauseGame: 
+					PlayMono( kClick );
+					SDL_WM_IconifyWindow();
+                    WaitForRegainFocus();
+					ItsTimeToRedraw();
+					return false;
+
+				case kRegisterNow:
+					PlayMono( kClick );
+					return true;
+					
+				case kControls:  
+					PlayMono( kClick );
+					return true;
+				
+				case kSecret:
+					if( ControlKeyIsPressed( ) )
+					{
+						*item = kWarp;
+						level = Warp( );
+						return true;
+					}
+					else if( OptionKeyIsPressed( ) )
+					{
+						//SoundTest( );
+						ItsTimeToRedraw();
+					}
+					return false;
+			}
+		}
+	}
+	else
+	{
+		lastDown = false;
+	}
+	
+	return false;
+}
+
+static MBoolean SharewareNoticeSelected( int *item, unsigned char inKey, SDLKey inSDLKey )
+{
+	MRect	 notYetRect    = { 340, 130, 360, 220 };
+	MRect    purchaseRect  = { 340, 260, 360, 365 };
+	MRect    enterCodeRect = { 340, 390, 360, 520 };
+	MPoint   p;
+	MBoolean button;
+
+	inKey, inSDLKey; // are unused
+	
+	*item = kNothing;
+	
+	if( !SharewareNoticeIsStillWaiting() )
+	{
+		SDLU_GetMouse( &p );
+		button = SDLU_Button();
+		
+		     if( MPointInMRect( p, &notYetRect ) )      *item = kSharewareNoticeNotYet;
+		else if( MPointInMRect( p, &enterCodeRect ) )   *item = kSharewareNoticeEnterCode;
+		else if( MPointInMRect( p, &purchaseRect ) )
+		{
+			*item = kSharewareNoticePurchase;
+			if( button )
+			{
+				WaitForRelease();
+				LaunchURL( "http://candycrisis.com/register.html" );
+				button = false;
+			}
+		}
+		
+		return (*item != kNothing) && button;
+	}
+	 
+	return false;
+}
+
+
+static MBoolean EnterCodeSelected( int *item, unsigned char inKey, SDLKey inSDLKey )
+{
+	MRect  okRect     = { 340, 150, 360, 220 };
+	MRect  notYetRect = { 340, 380, 360, 500 };
+	MRect  nameRect   = { 237, 140, 257, 500 };
+	MRect  keyRect    = { 301, 140, 321, 500 };
+	MPoint p;
+	int    fieldLength;
+	int    button;
+
+	inSDLKey; // is unused
+	
+	// -- Handle keyboard stuff. (Ripped off from high score code.)
+	fieldLength = strlen(whichField);
+	
+	// return or tab
+	if( inKey == 13 || inKey == 9 ) 
+	{
+		whichField = (whichField == nameField)? keyField: nameField;
+		PlayMono( kRotate );		
+	}
+	
+	// backspace
+	else if( inKey == 8 )
+	{
+		if( fieldLength > 0 )
+		{
+			whichField[ fieldLength-1 ] = '\0';
+			PlayMono( kClick );
+		}
+	}
+	
+	// characters
+	else if( (fieldLength < 40) && (smallFont->width[inKey] != 0) )
+	{
+		if( whichField == keyField ) inKey = toupper(inKey);
+		
+		whichField[ fieldLength++ ] = inKey;
+		whichField[ fieldLength   ] = '\0';
+		PlayMono( kPlace );
+	}
+
+	// -- Handle mouse.
+	button = SDLU_Button();
+
+	*item = kNothing;
+	SDLU_GetMouse( &p );
+	
+	     if( MPointInMRect( p, &notYetRect ) )        *item = kEnterCodeNotYet;
+	else if( MPointInMRect( p, &nameRect) && button ) whichField = nameField;
+	else if( MPointInMRect( p, &keyRect) && button )  whichField = keyField;
+    else if( MPointInMRect( p, &okRect ) )
+    {
+    	*item = kEnterCodeOK;
+
+    	if( button )
+		{
+			WaitForRelease();
+			playerIsRegistered = ValidateCode( nameField, keyField );
+			if( playerIsRegistered )
+			{
+				strcpy( registeredName, nameField );
+				strcpy( registeredKey, keyField );
+			}
+			else
+			{
+				batsuAlpha = 31;
+				PlayMono( kBatsuSnd );
+				button = false;
+			}
+		}
+	}
+	
+	return (*item != kNothing) && button;
+}
+
+
+void SharewareNotice( int forceWait )
+{
+	SDL_FillRect( frontSurface, &frontSurface->clip_rect, SDL_MapRGB( frontSurface->format, 40, 40, 40 ) );
+	SDL_Flip( frontSurface );
+	dialogUndimTime = MTickCount() + forceWait;
+
+	HandleDialog( kSharewareNoticeDialog );
+}
+
+
+void HandleDialog( int type )
+{	
+	const float    lighten[4] = { 12.0f, 6.0f, 1.0f, 6.0f };
+	const MRect    boardWorldZRect = {0, 0, kBlobVertSize * (kGridDown-1), kBlobHorizSize * kGridAcross};
+	const MRect    fullRect = { 0, 0, 480, 640 };
+	SDL_Rect       fullSDLRect = { 0, 0, 640, 480 };
+	SDL_Rect       joinSDLRect;
+	int            skip = 1;
+	int            count;
+	char           inASCII;
+	SDLKey         inSDLKey;
+	MRect          pauseRect, joinRect;
+	
+	// Clear state 
+	whichField = nameField;
+	nameField[0] = '\0';
+	keyField[0] = '\0';
+	batsuAlpha = 0;
+	controlToReplace = -1;
+	
+	// Remember dialog info
+	dialogType = type;
+	dialogStage = kOpening;
+	colorWrap = 0;
+	colorInc = (RandomBefore(250) + 250.0) / 10000.0;
+
+	smallFont      = GetFont( picFont );
+	tinyFont       = GetFont( picTinyFont );
+	bigFont        = GetFont( picHiScoreFont );
+	dashedLineFont = GetFont( picDashedLineFont );
+	continueFont   = GetFont( picContinueFont );
+	batsuFont      = GetFont( picBatsuFont );
+		
+	if( type == kSharewareNoticeDialog || type == kEnterCodeDialog )
+	{	
+		// People shouldn't enjoy the nag dialogs, so let's not have flashy animated colors.
+		for( count=0; count<4; count++ )
+		{
+			backColor[count].red   = 2;
+			backColor[count].green = 2;
+			backColor[count].blue  = 2;
+		}
+	}
+	else
+	{
+		// Pick some colors to animate.
+		for( count=0; count<4; count++ )
+		{
+			SDL_Color inColor;
+			
+			SDLU_GetPixel( boardSurface[0], RandomBefore( boardWorldZRect.right ), RandomBefore( boardWorldZRect.bottom ), &inColor );
+		
+			backColor[count].red   = inColor.r * (32.0f / 256.0f);
+			backColor[count].green = inColor.g * (32.0f / 256.0f);
+			backColor[count].blue  = inColor.b * (32.0f / 256.0f);
+
+			backColor[count].red   = min( 31.0f, backColor[count].red   + lighten[count] );
+			backColor[count].green = min( 31.0f, backColor[count].green + lighten[count] );
+			backColor[count].blue  = min( 31.0f, backColor[count].blue  + lighten[count] );
+		}
+	}
+	
+	// Get some graphics that we're going to need
+	logoSurface      = LoadPICTAsSurface( picLogo, 16 );
+	logoAlphaSurface = LoadPICTAsSurface( picLogoAlpha, 16 );
+	logoMaskSurface  = LoadPICTAsSurface( picLogoMask, 1 );
+
+	// Get a copy of the current game window contents
+	backSurface      = SDLU_InitSurface( &fullSDLRect, 16 );
+	
+	SDLU_BlitSurface( frontSurface, &frontSurface->clip_rect,
+	                  backSurface,  &backSurface->clip_rect );
+		
+	drawSurface      = SDLU_InitSurface( &fullSDLRect, 16 );
+
+	SDLU_BlitSurface( backSurface, &backSurface->clip_rect,
+	                  drawSurface, &drawSurface->clip_rect  );
+
+	//
+		
+	PlayMono( kWhomp );
+	dialogTimer = MTickCount();
+	dialogTarget = 0;
+	dialogShade = 0;
+	dialogStageComplete = false;
+	dialogItem = kNothing;
+	lastPauseRect.top = lastPauseRect.left = 9999;
+	lastPauseRect.bottom = lastPauseRect.right = -9999;
+
+	SDLU_StartWatchingTyping();
+	
+	DoFullRepaint = ItsTimeToRedraw;
+
+	while( ((dialogStage != kClosing) || !dialogStageComplete) && !finished )
+	{
+		dialogTimer += skip;
+
+		// Check mouse and keyboard
+		SDLU_CheckTyping( &inASCII, &inSDLKey );
+		
+		if( (dialogStage == kOpening) && dialogStageComplete )
+		{
+			MBoolean (*DialogSelected[kNumDialogs])( int *item, unsigned char inKey, SDLKey inSDLKey ) =
+			{
+				PauseSelected,
+				HiScoreSelected,
+				ContinueSelected,
+				RegisterSelected,
+				ControlsSelected,
+				SharewareNoticeSelected,
+				EnterCodeSelected
+			};
+			
+			if( DialogSelected[dialogType]( &dialogItem, inASCII, inSDLKey ) )
+			{
+				dialogStage = kClosing; 
+				dialogTarget = 0;
+			}
+		}
+
+		// Do animation ...
+		{
+			const MBoolean dialogIsLarge[kNumDialogs] = { false, false, false, false, true, true, true };
+
+			pauseRect = lastPauseRect;
+			dialogStageComplete = DrawDialogBox( dialogIsLarge[dialogType], dialogStage, &dialogTarget, skip, &colorWrap, colorInc, &pauseRect );
+			SurfaceGetEdges( backSurface, &pauseRect );
+		}
+
+		if( (dialogStage == kOpening) && dialogStageComplete )
+		{
+			void (*DialogDraw[kNumDialogs])( int *item, int shade ) =
+			{
+				DrawPauseContents,
+				DrawHiScoreContents,
+				DrawContinueContents,
+				DrawRegisterContents,
+				DrawControlsContents,
+				DrawSharewareNoticeContents,
+				DrawEnterCodeContents
+			};
+
+			// Refresh screen if necessary
+			if( timeToRedraw )
+			{
+				SDLU_BlitFrontSurface( backSurface, &fullSDLRect, &fullSDLRect );
+				timeToRedraw = false;
+			}
+			
+			// ... and fade in the logo
+
+			dialogShade += skip;
+
+			{
+				const MBoolean dialogHasCandyCrisisLogo[kNumDialogs] = { true, true, true, true, true, false, false };
+				
+				if( dialogHasCandyCrisisLogo[dialogType] )
+					DrawDialogLogo( &pauseRect, dialogShade );
+			}
+			
+			// ... and animation is complete so add content			
+			DialogDraw[dialogType]( &dialogItem, dialogShade );
+			
+			// ... and cursor
+			DrawDialogCursor( &pauseRect, &dialogShade );
+		}
+
+		SurfaceCurveEdges( drawSurface, &pauseRect );
+
+		// Draw new animation on screen
+		UnionMRect( &lastPauseRect, &pauseRect, &joinRect );
+		SDLU_MRectToSDLRect( &joinRect, &joinSDLRect );
+		SDLU_BlitFrontSurface( drawSurface, &joinSDLRect, &joinSDLRect );
+
+		lastPauseRect = pauseRect;
+
+		// Wait for next frame
+		if( dialogTimer <= MTickCount( ) )
+		{
+			dialogTimer = MTickCount( );
+			skip = 2;
+		}
+		else
+		{
+			skip = 1;
+			while( dialogTimer > MTickCount( ) ) 
+			{
+				SDLU_Yield();  
+			}
+		}
+	}
+	
+	DoFullRepaint = NoPaint;
+
+	SDLU_StopWatchingTyping();
+
+	// Bring back previous screen
+	SDLU_BlitFrontSurface( backSurface, &fullSDLRect, &fullSDLRect );
+
+	// Dispose the GWorlds and fonts we used
+	SDL_FreeSurface( backSurface );
+	SDL_FreeSurface( drawSurface );
+	SDL_FreeSurface( logoSurface );
+	SDL_FreeSurface( logoAlphaSurface );
+	SDL_FreeSurface( logoMaskSurface );
+			
+	switch( dialogItem )
+	{
+		case kRegisterNow:
+			SharewareNotice( 0 );
+			RefreshAll();
+			break;
+		
+		case kSharewareNoticeEnterCode:
+			HandleDialog( kEnterCodeDialog );
+			break;
+			
+		case kEnterCodeNotYet:
+			HandleDialog( kSharewareNoticeDialog );
+			break;
+			
+		case kControls:
+			HandleDialog( kControlsDialog );
+			HandleDialog( kPauseDialog );
+			break;
+		
+		case kEndGame:
+			ChooseMusic( -1 );
+			AddHiscore( score[0] );
+			if( players == 1 )
+			{
+				ShowGameOverScreen( );
+			}
+			else
+			{
+				QuickFadeOut(NULL);
+			}
+			
+			showStartMenu = true;
+			break;
+		
+		case kContinue:
+			displayedScore[0] = score[0] = roundStartScore[0];
+			ShowScore( 0 );
+			BeginRound( true );
+			break;
+			
+		case kWarp:
+		{
+			int newLevel = level;
+			
+			InitGame( OptionKeyIsPressed()? kAIControl: kPlayerControl, kAIControl ); // this clears "level" ...
+			level = newLevel;                                                         // so we need to set "level" afterwards
+			BeginRound( true );	
+			break;
+		}
+	}
+}
--- /dev/null
+++ b/Source/pause.h
@@ -1,0 +1,18 @@
+// pause.h
+
+void SharewareNotice( int forcedWait );
+void HandleDialog( int type );
+void SurfaceGetEdges( SDL_Surface* edgeSurface, const MRect *rect );
+void SurfaceCurveEdges( SDL_Surface* edgeSurface, const MRect *rect );
+
+enum
+{
+	kPauseDialog = 0,
+	kHiScoreDialog,
+	kContinueDialog,
+	kRegisterDialog,
+	kControlsDialog,
+	kSharewareNoticeDialog,
+	kEnterCodeDialog,
+	kNumDialogs
+};
--- /dev/null
+++ b/Source/players.cpp
@@ -1,0 +1,1079 @@
+// players.c
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include "main.h"
+#include "players.h"
+#include "gworld.h"
+#include "moving.h"
+#include "graphics.h"
+#include "control.h"
+#include "grays.h"
+#include "soundfx.h"
+#include "next.h"
+#include "random.h"
+#include "victory.h"
+#include "tweak.h"
+#include "zap.h"
+#include "level.h"
+#include "opponent.h"
+#include "gameticks.h"
+#include "blitter.h"
+#include "music.h"
+#include "score.h"
+#include "hiscore.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+unsigned long boredTime[2], hintTime[2], fadeCharTime[2], animTime[2], shadowDepth[2], hintGlow, messageTime;
+int emotions[2];
+int glowColors[][3] = { { 0,  0,  0},
+						{13, 26, 31},
+						{13, 29, 13},
+						{31, 18, 31},
+						{31, 14, 18},
+						{31, 31, 15},
+						{31, 21, 13},
+						{30, 22, 30},
+						{20, 20, 20} };
+
+void HandlePlayers( void )
+{
+	int player;
+	
+	for( player = 0; player<=1; player++ )
+	{
+		UpdateScore( player );
+		UpdateNext( player );
+		BlinkGrays( player );
+		GlowBlobs( player );
+		BlinkBored( player );
+		FadeCharred( player );
+
+		switch( role[player] )
+		{
+			case kWaitForRetrieval:
+				if( control[player] == kAutoControl )
+				{
+					AutoControl( player );
+					break;
+				}
+				
+				role[player] = kRetrieveBlobs;
+				// fallthrough
+				
+			case kRetrieveBlobs:
+				LockGrays( 1-player );
+				RetrieveBlobs( player );
+				break;
+				
+			case kFalling:
+				Falling( player );
+				break;
+			
+			case kLockdownBlobs:
+				LockdownBlobs( player );
+				break;
+				
+			case kJiggleBlobs:
+				JiggleBlobs( player );
+				break;
+				
+			case kFastJiggleBlobs:
+				FastJiggleBlobs( player );
+				break;
+				
+			case kPlaceBlobs:
+				PlaceBlobs( player );
+				break;
+			
+			case kDropBlobs:
+				DropBlobs( player );
+				break;
+			
+			case kZapBlobs:
+				ZapBlobs( player );
+				break;
+			
+			case kKillBlobs:
+				KillBlobs( player );
+				break;
+			
+			case kDropGrays:
+				DropGrays( player );
+				break;
+			
+			case kLosing:
+				Lose( player );
+				break;
+			
+			case kWinning:
+				Win( player );
+				break;
+				
+			case kChooseDifficulty:
+				ChooseDifficulty( player );
+				break;
+			
+			case kWaitingToStart:
+				if( role[1-player] == kWaitingToStart )
+					role[0] = role[1] = kZapBlobs;
+				break;
+			
+			case kIdlePlayer:
+				break;
+		}
+		
+		UpdatePlayerWindow( player );
+	}
+}
+
+void LockdownBlobs( int player )
+{
+	const int shadowList[] = { 
+								 kBlobShadowDepth, 
+								 kBlobShadowDepth*5/6, 
+								 kBlobShadowDepth*5/6, 
+								 kBlobShadowDepth*4/6, 
+								 kBlobShadowDepth*3/6, 
+								 kBlobShadowDepth*1/6, 
+								};
+
+	int frame = (GameTickCount( ) - blobTime[player]) / 2;
+
+	if( frame >= 8 )
+	{
+		role[player] = kPlaceBlobs;
+		return;
+	}
+	
+	shadowDepth[player] = shadowList[frame];
+	UpdateTweak( player, blobJiggleAnimation );
+}
+
+void FastJiggleBlobs( int player )
+{
+	const int shadowList[] = { 
+								 kBlobShadowDepth, 
+								 kBlobShadowDepth*5/6, 
+								 kBlobShadowDepth*5/6, 
+								 kBlobShadowDepth*4/6, 
+								 kBlobShadowDepth*3/6, 
+								 kBlobShadowDepth*2/6, 
+								 kBlobShadowDepth*1/6,
+								 0 
+								};
+
+	int frame = (GameTickCount( ) - blobTime[player]) / 2;
+
+	if( frame >= 8 )
+	{
+		role[player] = kPlaceBlobs;
+		return;
+	}
+
+	switch( control[player] )
+	{
+		case kPlayerControl:
+			PlayerControl( player );
+			break;
+		
+		case kAIControl:
+			AIControl( player );
+			break;
+		
+		case kAutoControl:
+			AutoControl( player );
+			break;
+	}
+	
+	shadowDepth[player] = shadowList[frame];
+	UpdateTweak( player, blobJiggleAnimation );
+
+	if( CanFall( player ) )
+		role[player] = kFalling;
+}
+
+
+void JiggleBlobs( int player )
+{
+	const int shadowList[] = { 
+								 kBlobShadowDepth, kBlobShadowDepth,
+								 kBlobShadowDepth*5/6, kBlobShadowDepth*5/6,
+								 kBlobShadowDepth*5/6, kBlobShadowDepth*4/6,
+								 kBlobShadowDepth*4/6, kBlobShadowDepth*3/6,
+								 kBlobShadowDepth*3/6, kBlobShadowDepth*2/6,
+								 kBlobShadowDepth*2/6, kBlobShadowDepth*1/6,
+								 kBlobShadowDepth*1/6, 0
+								};
+
+	int frame = (GameTickCount( ) - blobTime[player]) / 2;
+	
+	if( frame >= 14 )
+	{
+		role[player] = kPlaceBlobs;
+		return;
+	}
+
+	switch( control[player] )
+	{
+		case kPlayerControl:
+			PlayerControl( player );
+			break;
+		
+		case kAIControl:
+			AIControl( player );
+			break;
+			
+		case kAutoControl:
+			AutoControl( player );
+			break;
+	}
+
+	shadowDepth[player] = shadowList[frame];
+	UpdateTweak( player, kJiggleAnimation );
+	
+	if( CanFall( player ) )
+		role[player] = kFalling;
+}
+
+void PlaceGrenade( int player )
+{
+	MRect myRect;
+	int x, y, atX, atY, color, delay = -6;
+	int currentX = blobX[player], currentY = blobY[player];
+	int charTypes[3][5] = { { kDarkChar | kChar11, kDarkChar | kChar12, kDarkChar | kChar13, kDarkChar | kChar14, kNoCharring         },
+	                        { kNoCharring,         kNoCharring,         kNoCharring,         kNoCharring,         kDarkChar | kChar24 },
+	                        { kDarkChar | kChar31, kDarkChar | kChar32, kDarkChar | kChar33, kDarkChar | kChar34, kNoCharring         } };
+	
+	int amount = ((level <= kLevels)? level: 1) * 100;
+	int multiplier = 0;
+	
+	grenadeFrame[player] = 0;
+	grenadeRect [player].top    = (currentY * kBlobVertSize ) - (kBlastHeight/2);
+	grenadeRect [player].left   = (currentX * kBlobHorizSize) + (kBlobHorizSize/2) - (kBlastWidth/2);
+	grenadeRect [player].bottom = grenadeRect[player].top  + kBlastHeight;
+	grenadeRect [player].right  = grenadeRect[player].left + kBlastWidth ;
+
+	SDLU_AcquireSurface( playerSurface[player] );
+	
+	for( x=-1; x<=1; x++ )
+	{
+		atX = currentX + x;
+		
+		for( y=-2; y<=2; y++ )
+		{
+			atY = currentY + y;
+
+			if( atX >= 0 && atX < kGridAcross &&
+			    atY >= 0 && atY < kGridDown )
+			{
+				if( charTypes[x+1][y+2] != kNoCharring   &&
+				    grid[player][atX][atY] >= kFirstBlob &&
+					grid[player][atX][atY] <= kLastBlob     )
+				{
+					charred[player][atX][atY] =  charTypes[x+1][y+2];
+
+					CalcBlobRect( atX, atY, &myRect );
+					SurfaceDrawBlob( player, &myRect, grid[player][atX][atY], suction[player][atX][atY], charred[player][atX][atY] );
+					CleanSpriteArea( player, &myRect );
+				}
+			}
+		}
+	}
+
+	SDLU_ReleaseSurface( playerSurface[player] );
+	
+	if( currentY < (kGridDown-1) && 
+		( grid[player][currentX][currentY+1] >= kFirstBlob && 
+		  grid[player][currentX][currentY+1] <= kLastBlob    ) )
+	{
+		color = grid[player][currentX][currentY+1];
+		
+		for( x=0; x<kGridAcross; x++ )
+		{
+			for( y=0; y<kGridDown; y++ )
+			{
+				if( grid[player][x][y] == color )
+				{
+					suction[player][x][y] = kInDeath;
+					death[player][x][y] = -abs( x - currentX + y - currentY );
+					multiplier++;
+					
+					if( (x <= (kGridAcross-2)) && (grid[player][x+1][y] == kGray) )
+					{
+						suction[player][x+1][y] = kGrayBlink1;
+						death[player][x+1][y] = delay;
+					}
+
+					if( (x >= 1) && (grid[player][x-1][y] == kGray) )
+					{
+						suction[player][x-1][y] = kGrayBlink1;
+						death[player][x-1][y] = delay;
+					}
+
+					if( (y <= (kGridDown-2)) && (grid[player][x][y+1] == kGray) )
+					{
+						suction[player][x][y+1] = kGrayBlink1;
+						death[player][x][y+1] = delay;
+					}
+
+					if( (y >= 1) && (grid[player][x][y-1] == kGray) )
+					{
+						suction[player][x][y-1] = kGrayBlink1;
+						death[player][x][y-1] = delay;
+					}
+				}
+			}
+		}
+	}
+	else
+	{
+		for( x=currentX-1; x<=currentX+1; x++ )
+		{
+			for( y=currentY-1; y<=currentY+1; y++ )
+			{
+				if( x>=0 && x<kGridAcross && y>=0 && y<kGridDown )
+				{
+					if( grid[player][x][y] == kGray )
+					{
+						suction[player][x][y] = kGrayBlink1;
+						death[player][x][y] = delay;
+					}
+					else if( grid[player][x][y] >= kFirstBlob &&
+					         grid[player][x][y] <= kLastBlob     )
+					{
+						suction[player][x][y] = kInDeath;
+						death[player][x][y] = -abs( x - currentX + y - currentY );
+						multiplier++;
+					}
+				}
+			}
+		}
+	}
+	
+	PlayStereo( player, kSplop );
+
+	if( multiplier > 0 )
+	{
+		score[player] += amount * multiplier;	
+		ZapScoreDisplay( player, amount, multiplier, blobX[player], blobY[player], 8 );
+	}
+	
+	blobTime[player] = GameTickCount( );
+	role[player] = kKillBlobs;
+}
+
+void PlaceBlobs( int player )
+{
+	MRect myRect;
+	int x, y, height, delay = -6;
+	int currentX = blobX[player], currentY = blobY[player];
+	
+	potentialCombo[player].x = currentX;
+	potentialCombo[player].r = blobR[player];
+
+	SDLU_AcquireSurface( playerSurface[player] );
+
+	for( x=0; x<kGridAcross; x++ )
+	{
+		for( y=0; y<kGridDown; y++ )
+		{
+			if( glow[player][x][y] )
+			{
+				glow[player][x][y] = false;
+				CalcBlobRect( x, y, &myRect );
+				SurfaceDrawBlob( player, &myRect, grid[player][x][y], suction[player][x][y], charred[player][x][y] );
+				CleanSpriteArea( player, &myRect );
+			}
+		}
+	}
+
+	SDLU_ReleaseSurface( playerSurface[player] );
+
+	EraseSpriteBlobs( player );
+	
+	CalcBlobRect( currentX, currentY, &myRect );
+	CalcSecondBlobOffset( player, &x, &y );
+	
+	if( grenade[player] )
+	{
+		PlaceGrenade( player );	
+		return;
+	}
+	
+	if( magic[player] )
+	{
+		switch( blobR[player] )
+		{
+			case upRotate:
+				height = GetRowHeight(player, currentX)-1;
+				grid[player][currentX][height] = colorB[player];
+				colorA[player] = BestColor( player, currentX, height+1 );
+				grid[player][currentX][height] = kEmpty;
+				break;
+			
+			case downRotate:
+				height = GetRowHeight(player, currentX);
+				grid[player][currentX][height] = colorB[player];
+				colorA[player] = BestColor( player, currentX, height-1 );
+				grid[player][currentX][height] = kEmpty;
+				break;
+			
+			case rightRotate:
+				height = GetRowHeight(player, currentX+1);
+				grid[player][currentX+1][height] = colorB[player];
+				colorA[player] = BestColor( player, currentX, GetRowHeight(player, currentX) );
+				grid[player][currentX+1][height] = kEmpty;
+				break;
+
+			case leftRotate:
+				height = GetRowHeight(player, currentX-1);
+				grid[player][currentX-1][height] = colorB[player];
+				colorA[player] = BestColor( player, currentX, GetRowHeight(player, currentX) );
+				grid[player][currentX-1][height] = kEmpty;
+				break;
+		}
+	}
+	
+	SDLU_AcquireSurface( playerSurface[player] );
+
+	if( currentX >= 0 && currentX < kGridAcross &&
+		currentY >= 0 && currentY < kGridDown )
+	{	
+		grid[player][currentX][currentY] = colorA[player];
+		suction[player][currentX][currentY] = kNoSuction;
+		charred[player][currentX][currentY] = kNoCharring;
+
+		SurfaceDrawBlob( player, &myRect, colorA[player], kNoSuction, kNoCharring );
+		CleanSpriteArea( player, &myRect );
+	}
+	
+	OffsetMRect( &myRect, x * kBlobHorizSize, y * kBlobVertSize );
+	currentX += x; 
+	currentY += y;
+	
+	if( currentX >= 0 && currentX < kGridAcross &&
+		currentY >= 0 && currentY < kGridDown )
+	{
+		grid[player][currentX][currentY] = colorB[player];
+		suction[player][currentX][currentY] = kNoSuction;
+		charred[player][currentX][currentY] = kNoCharring;
+		
+		SurfaceDrawBlob( player, &myRect, colorB[player], kNoSuction, kNoCharring );
+		CleanSpriteArea( player, &myRect );
+	}
+	
+	SDLU_ReleaseSurface( playerSurface[player] );
+	
+	blobTime[player] = GameTickCount( );
+	halfway[player] = false;
+	role[player] = kDropBlobs;
+}
+
+void DropBlobs( int player )
+{
+	MBoolean busy = false;
+	signed char tempG[kGridDown], tempC[kGridDown];
+	const int jiggleList[] = { kNoSuction, kSquish,
+					      	   kNoSuction, kSquash,
+						  	   kNoSuction, kSquish,
+						  	   kNoSuction, kSquash };
+	MRect myRect;
+	int x, y;
+	
+	if( GameTickCount( ) < blobTime[player] )
+		return;
+	
+	blobTime[player] += 1;
+	
+	halfway[player] = !halfway[player];
+	
+	SDLU_AcquireSurface( playerSurface[player] );
+	
+	if( halfway[player] )
+	{
+		for( x=0; x<kGridAcross; x++ )
+		{
+			for( y = 0; y<kGridDown; y++ )
+			{
+				tempG[y] = grid[player][x][y];
+				tempC[y] = charred[player][x][y];
+			}
+			
+			for( y = kGridDown-1; y; y-- )
+			{
+				if( tempG[y] == kEmpty && tempG[y-1] != kEmpty )
+				{
+					CalcBlobRect( x, y, &myRect );
+					OffsetMRect( &myRect, 0, -kBlobVertSize/2 );
+					
+					if( tempG[y-1] == kGray )
+					{
+						SurfaceDrawBoard( player, &myRect );
+						SurfaceDrawAlpha( &myRect, kGray, kLight, kGrayNoBlink );
+					}
+					else
+					{
+						SurfaceDrawBlob( player, &myRect, tempG[y-1], kNoSuction, tempC[y-1] );
+					}
+					
+					tempG[y]   = tempG[y-1];
+					tempG[y-1] = kEmpty;
+					tempC[y]   = tempC[y-1];
+					tempC[y-1] = kNoCharring;
+
+					CleanSpriteArea( player, &myRect );
+					
+					OffsetMRect( &myRect, 0, -kBlobVertSize );
+					SurfaceDrawBoard( player, &myRect );
+					CleanSpriteArea( player, &myRect );
+				}
+			}
+		}
+	}
+	else
+	{
+		for( x=0; x<kGridAcross; x++ )
+		{
+			for( y = kGridDown-1; y; y-- )
+			{
+				if( suction[player][x][y] >= kJiggle1 &&
+					suction[player][x][y] <  kInDoubt )
+				{
+					CalcBlobRect( x, y, &myRect );
+					SurfaceDrawBlob( player, &myRect, grid[player][x][y],
+						jiggleList[ suction[player][x][y] - kJiggle1 ],
+						charred[player][x][y] );
+					CleanSpriteArea( player, &myRect );
+					
+					suction[player][x][y]++;
+					
+					busy = true;
+				}
+				else if( grid[player][x][y] == kEmpty && grid[player][x][y-1] != kEmpty )
+				{
+					grid[player][x][y] = grid[player][x][y-1];
+					grid[player][x][y-1] = kEmpty;
+					charred[player][x][y] = charred[player][x][y-1];
+					charred[player][x][y-1] = kNoCharring;
+					suction[player][x][y-1] = kNoSuction;
+					
+					CalcBlobRect( x, y, &myRect );
+					if( grid[player][x][y] == kGray )
+					{
+						SurfaceDrawBoard( player, &myRect );
+						SurfaceDrawAlpha( &myRect, kGray, kLight, kGrayNoBlink );
+					}
+					else
+					{
+						SurfaceDrawBlob( player, &myRect, grid[player][x][y], kNoSuction, charred[player][x][y] );
+					}
+
+					CleanSpriteArea( player, &myRect );
+					
+					OffsetMRect( &myRect, 0, -kBlobVertSize );
+					SurfaceDrawBoard( player, &myRect );
+					CleanSpriteArea( player, &myRect );
+					
+					if( grid[player][x][y] >= kFirstBlob && grid[player][x][y] <= kLastBlob )
+					{
+						if( y >= (kGridDown-1) || grid[player][x][y+1] != kEmpty )
+						{
+							suction[player][x][y] = kJiggle1;
+						}
+					}
+					else
+					{
+						suction[player][x][y] = kNoSuction;
+					}
+					
+					busy = true;
+				}
+			}
+		}
+	}
+	
+	SDLU_ReleaseSurface( playerSurface[player] );
+	
+	if( !busy && !halfway[player] )
+	{
+		ResolveSuction( player );
+		role[player] = kZapBlobs;
+	}
+}
+
+void RedrawBoardContents( int player )
+{
+	int x, y;
+	MRect myRect;
+	
+	SDLU_AcquireSurface( playerSurface[player] );
+	
+	for( y=0; y<kGridDown; y++ )
+	{
+		for( x=0; x<kGridAcross; x++ )
+		{
+			CalcBlobRect( x, y, &myRect );
+			if( grid[player][x][y] == kGray )
+			{
+				SurfaceDrawBoard( player, &myRect );
+				SurfaceDrawAlpha( &myRect, kGray, kLight, kGrayNoBlink );
+			}
+			else
+			{
+				SurfaceDrawBlob( player, &myRect, grid[player][x][y], suction[player][x][y], charred[player][x][y] );
+			}
+			
+			CleanSpriteArea( player, &myRect );
+		}
+	}
+	
+	SDLU_ReleaseSurface( playerSurface[player] );
+}
+
+void ResolveSuction( int player )
+{
+	int x, y, suck, actualSuck, color;
+	MRect myRect;
+	
+	SDLU_AcquireSurface( playerSurface[player] );
+	
+	for( x=0; x<kGridAcross; x++ )
+	{
+		for( y=1; y<kGridDown; y++ )
+		{
+			suck = kNoSuction;
+			color = grid[player][x][y];
+			
+			if( color >= kFirstBlob && color <= kLastBlob )
+			{
+				if( x > 0 )
+					if( grid[player][x-1][y] == color ) suck |= kLeft;
+				
+				if( x < (kGridAcross-1) )
+					if( grid[player][x+1][y] == color ) suck |= kRight;
+					
+				if( y > 1 )
+					if( grid[player][x][y-1] == color ) suck |= kUp;
+					
+				if( y < (kGridDown-1) )
+					if( grid[player][x][y+1] == color ) suck |= kDown;
+				
+				actualSuck = suction[player][x][y];
+				if( actualSuck == kBlinkBlob || actualSuck == kSobBlob ) actualSuck = kNoSuction;
+				
+				if( actualSuck != suck )
+				{
+					suction[player][x][y] = suck;
+					
+					CalcBlobRect( x, y, &myRect );
+					SurfaceDrawBlob( player, &myRect, grid[player][x][y], suck, charred[player][x][y] );
+					CleanSpriteArea( player, &myRect );
+				}
+			}
+			else
+			{
+				suction[player][x][y] = kNoSuction;
+			}
+		}
+	}
+	
+	SDLU_ReleaseSurface( playerSurface[player] );
+}
+
+void HandleMagic( int player )
+{
+	if( magic[player] )
+	{
+		colorA[player]++;
+		if( colorA[player] > kBlobTypes ) colorA[player] = 1;
+	}
+}
+
+void Falling( int player )
+{	
+	if( role[1-player] == kLosing )
+	{
+		BeginVictory( player );
+		return;
+	}
+	
+	shadowDepth[player] = kBlobShadowDepth;
+	
+	UpdateTweak( player, kNoSuction );
+
+	if( GameTickCount( ) >= blobTime[player] )
+	{
+		if( CanFall( player ) )
+		{
+			blobTime[player] += dropping[player]? kDropSpeed: speed[player];
+			DoFall( player );
+		}
+		else
+		{
+			blobTime[player] = animTime[player] = GameTickCount( );
+			role[player] = dropping[player]? kFastJiggleBlobs: kJiggleBlobs;
+			anim[player] = 0;
+			PlayStereoFrequency( player, kPlace, player );
+			return;
+		}
+	}
+
+	switch( control[player] )
+	{
+		case kPlayerControl:
+			PlayerControl( player );
+			break;
+		
+		case kAIControl:
+			AIControl( player );
+			break;
+
+		case kAutoControl:
+			AutoControl( player );
+			break;
+	}
+}
+
+void RetrieveBlobs( int player )
+{
+	if( (role[1-player] != kLosing) && (grid[player][2][1] != kEmpty) )
+	{
+		EndRound( player );
+		return;
+	}
+	
+	// See if it's time to update levels in Solitaire mode
+	if( control[1] == kNobodyControl )
+	{
+		int levelClear[] = { 0,
+		                     2500,
+		                     7000,
+		                     14000,
+		                     35000,
+		                     50000,
+		                     70000,
+		                     90000,
+		                     120000,
+		                     160000,
+		                     200000,
+		                     500000,
+		                     1000000 };
+		
+		if( (level <= kLevels) && (score[player] > levelClear[level]) )
+		{
+			FreezeGameTickCount( );
+			
+			PlayMono( kLevelUp );
+			
+			level++;
+			if( InitCharacter( 1, level ) )
+			{
+				InitCharacter( 1, level );
+				character[0] = character[1];
+				character[0].zapStyle = 0;
+			}
+			else
+			{
+				UnfreezeGameTickCount( );
+				TotalVictory( );
+				return;
+			}
+
+			if( level == 2 || level == 4 || level == 7 || level == 9 ) AddExtraPiece( );
+			
+			PrepareStageGraphics( character[1].picture );
+			ChooseMusic( character[1].music );
+
+			UnfreezeGameTickCount( );
+		}		                     
+	}
+	
+	PullNext( player );
+	
+	dropping[player] = false;
+	anim[player] = 0;
+	magic[player] = nextM[player];
+	grenade[player] = nextG[player];
+	zapIteration[player] = 0;
+	chain[player] = 1;
+	
+	emotions[player] = DetermineEmotion(player);
+	if( players == 1 && player == 0 ) 
+	{
+		if( emotions[0] == kEmotionPanic ) FastMusic(); else SlowMusic();
+	}
+	
+	if( magic[player] || grenade[player] )
+	{
+		PlayStereoFrequency( player, kMagic, player );
+	}
+	
+	colorA[player] = nextA[player];
+	colorB[player] = nextB[player];
+	
+	nextG[player] = GetGrenade( player );
+
+	if( nextG[player] )
+	{
+		nextA[player] = kBombBottom;
+		nextB[player] = kBombTop;
+		nextM[player] = false;
+	}
+	else
+	{
+		nextA[player] = GetPiece( player );
+		nextB[player] = GetPiece( player );
+		nextM[player] = GetMagic( player );
+	}
+
+	ChooseGlowingBlobs( player );
+	
+	if( control[player] == kPlayerControl )
+	{
+		memcpy( potentialCombo[player].grid, grid[player], kGridAcross * kGridDown );
+		potentialCombo[player].a = colorA[player];
+		potentialCombo[player].b = colorB[player];
+		potentialCombo[player].m = magic[player];
+		potentialCombo[player].g = grenade[player];
+		potentialCombo[player].lv = level;
+		potentialCombo[player].x = 0;
+		potentialCombo[player].r = 0;
+		potentialCombo[player].player = player;
+		potentialCombo[player].value = 0;
+		potentialCombo[player].name[0] = 0;
+	}
+	
+	blobX[player] = 2;
+	blobY[player] = 0;
+	blobR[player] = upRotate;
+	blobSpin[player] = 0;
+	halfway[player] = false;
+	
+	DrawSpriteBlobs( player, kNoSuction );
+	
+	speed[player] = character[player].dropSpeed;
+	blobTime[player] = animTime[player] = GameTickCount( );
+	role[player] = kFalling;
+	
+	if( control[player] == kAIControl )
+		ChooseAIDestination( player );
+}
+
+void ChooseGlowingBlobs( int player )
+{
+	int x, height[kGridAcross];
+	
+    if( character[player].hints && !grenade[player] && !magic[player] )
+    {
+		for( x=0; x<kGridAcross; x++ )
+		{
+			height[x] = GetRowHeight( player, x );
+		}
+				
+		for( x=0; x<kGridAcross; x++ )
+		{
+			if( x>0 && height[x]>1 && height[x-1]>1 ) 
+			{
+				// left
+
+				grid[player][x  ][height[x  ]] = colorA[player];
+				grid[player][x-1][height[x-1]] = colorB[player];
+				ConsiderGlow( player, colorA[player], x, height[x  ] );
+				ConsiderGlow( player, colorB[player], x, height[x-1] );
+				grid[player][x  ][height[x  ]] = kEmpty;
+				grid[player][x-1][height[x-1]] = kEmpty;
+			}
+
+			if( x<(kGridAcross-1) && height[x]>1 && height[x+1]>1 ) 
+			{
+				// right
+
+				grid[player][x  ][height[x  ]] = colorA[player];
+				grid[player][x+1][height[x+1]] = colorB[player];
+				ConsiderGlow( player, colorA[player], x, height[x  ] );
+				ConsiderGlow( player, colorB[player], x, height[x+1] );
+				grid[player][x  ][height[x  ]] = kEmpty;
+				grid[player][x+1][height[x+1]] = kEmpty;
+			}
+
+			if( height[x]>2 ) 
+			{
+				// up
+
+				grid[player][x][height[x]  ] = colorA[player];
+				grid[player][x][height[x]-1] = colorB[player];
+				ConsiderGlow( player, colorA[player], x, height[x]   );
+				ConsiderGlow( player, colorB[player], x, height[x]-1 );
+				grid[player][x][height[x]  ] = kEmpty;
+				grid[player][x][height[x]-1] = kEmpty;
+
+				// down
+
+				grid[player][x][height[x]  ] = colorB[player];
+				grid[player][x][height[x]-1] = colorA[player];
+				ConsiderGlow( player, colorB[player], x, height[x]   );
+				ConsiderGlow( player, colorA[player], x, height[x]-1 );
+				grid[player][x][height[x]  ] = kEmpty;
+				grid[player][x][height[x]-1] = kEmpty;
+			}
+		}
+	}
+}
+
+void ConsiderGlow( int player, int color, int x, int y )
+{
+	if( GetChainSize( grid[player], x, y, color ) >= kBlobClusterSize )
+	{
+		CleanWithPolish( grid[player], glow[player], x, y, color );					
+	}
+	else
+	{
+		CleanSize( grid[player], x, y, color );
+	}
+}
+
+void GlowBlobs( int player )
+{
+	int x, y, color, suck;
+	MRect myRect;
+	
+	if( (!character[player].hints) || (GameTickCount() < hintTime[player]) )
+		return;
+		
+	hintTime[player] += 3;
+	if( ++hintGlow >= kGlowArraySize ) hintGlow = 0;
+
+	SDLU_AcquireSurface( playerSurface[player] );
+
+	for( x=0; x<kGridAcross; x++ )
+	{
+		if( rowBounce[player][x] == -1 )
+		{
+			for( y=0; y<kGridDown; y++ )
+			{
+				if( glow[player][x][y] && grid[player][x][y] != kEmpty )
+				{
+					CalcBlobRect( x, y, &myRect );	
+
+					color = grid[player][x][y];
+					suck = suction[player][x][y];
+					
+					SurfaceDrawBlob( player, &myRect, color, suck, charred[player][x][y] );
+					SurfaceDrawColor( &myRect, color, suck, glowColors[color][0], glowColors[color][1], glowColors[color][2], glowArray[hintGlow] );
+					CleanSpriteArea( player, &myRect );
+				}
+			}
+		}
+	}
+	
+	SDLU_ReleaseSurface( playerSurface[player] );
+}
+
+void FadeCharred( int player )
+{
+	int x, y;
+	MRect myRect;
+	
+	if( GameTickCount() < fadeCharTime[player] || role[player] != kFalling ) return;
+	
+	fadeCharTime[player] += 135;
+	
+	SDLU_AcquireSurface( playerSurface[player] ); 
+	
+	for( x=0; x<kGridAcross; x++ )
+	{
+		for( y=0; y<kGridDown; y++ )
+		{
+			if( charred[player][x][y] & 0xF0 )
+			{
+				charred[player][x][y] = ( charred[player][x][y] & 0x0F ) | ( ( charred[player][x][y] & 0xF0 ) - 0x10 );
+				
+				if( rowBounce[player][x] == -1 )
+				{
+					CalcBlobRect( x, y, &myRect );
+					SurfaceDrawBlob( player, &myRect, grid[player][x][y], suction[player][x][y], charred[player][x][y] );
+					CleanSpriteArea( player, &myRect );
+				}
+			}
+		}
+	}
+
+	SDLU_ReleaseSurface( playerSurface[player] ); 
+}
+
+void BlinkBored( int player )
+{
+	MRect myRect;
+	int which, x, y, count;
+	
+	if( GameTickCount() < boredTime[player] )
+		return;
+	
+	SDLU_AcquireSurface( playerSurface[player] ); 
+	
+	// undo any previously bored blobs
+	
+	for( x=0; x<kGridAcross; x++ )
+	{
+		for( y=0; y<kGridDown; y++ )
+		{
+			if( ( rowBounce[player][x] == -1 ) &&
+				( suction[player][x][y] == kBlinkBlob || suction[player][x][y] == kSobBlob ) )
+			{
+				suction[player][x][y] = kNoSuction;
+				
+				CalcBlobRect( x, y, &myRect );
+				SurfaceDrawBlob( player, &myRect, grid[player][x][y],
+								   suction[player][x][y],
+								   charred[player][x][y] );
+				CleanSpriteArea( player, &myRect );
+			}				
+		}
+	}
+		
+	// make some boredom
+
+	which = RandomBefore( 2 );
+	boredTime[player] += which? 15: 80;
+	which = which? kBlinkBlob: kSobBlob;
+
+	for( count=0; count<5; count++ )
+	{
+		x = RandomBefore( kGridAcross );
+		y = RandomBefore( kGridDown );
+		
+		if( rowBounce[player][x] == -1 &&
+			suction[player][x][y] == kNoSuction &&
+			grid[player][x][y] >= kFirstBlob &&
+			grid[player][x][y] <= kLastBlob   
+		  ) 
+		{
+			suction[player][x][y] = which;
+			
+			CalcBlobRect( x, y, &myRect );
+			SurfaceDrawBlob( player, &myRect,
+					    grid[player][x][y],
+					    suction[player][x][y],
+					    charred[player][x][y] );
+			CleanSpriteArea( player, &myRect );
+		}
+	}
+
+	SDLU_ReleaseSurface( playerSurface[player] ); 
+}
+
+void InitPlayers( void )
+{
+	const double windowLoc[ ] = { kLeftPlayerWindowCenter, kRightPlayerWindowCenter };
+	
+	playerWindowZRect.top = playerWindowZRect.left = 0;
+	playerWindowZRect.bottom = 288; playerWindowZRect.right = 144;
+	
+	playerWindowRect[0] = playerWindowRect[1] = playerWindowZRect;
+	CenterRectOnScreen( &playerWindowRect[0], windowLoc[0], 0.5 );
+	CenterRectOnScreen( &playerWindowRect[1], windowLoc[1], 0.5 );
+}
--- /dev/null
+++ b/Source/players.h
@@ -1,0 +1,52 @@
+// players.h
+
+void HandlePlayers( void );
+void RetrieveBlobs( int player );
+void Falling( int player );
+void PlaceBlobs( int player );
+void DropBlobs( int player );
+void ResolveSuction( int player );
+void BlinkBored( int player );
+void InitPlayers( void );
+void JiggleBlobs( int player );
+void FastJiggleBlobs( int player );
+void LockdownBlobs( int player );
+void HandleMagic( int player );
+void ChooseGlowingBlobs( int player );
+void ConsiderGlow( int player, int color, int x, int y );
+void GlowBlobs( int player );
+void PlaceGrenade( int player );
+void FadeCharred( int player );
+void RedrawBoardContents( int player );
+
+extern unsigned long boredTime[2], hintTime[2], fadeCharTime[2], animTime[2], shadowDepth[2], messageTime;
+extern MBoolean idling[2];
+extern int emotions[2];
+extern int glowColors[][3];
+
+enum
+{
+	kFalling = 0,
+	kRetrieveBlobs,
+	kPlaceBlobs,
+	kJiggleBlobs,
+	kFastJiggleBlobs,
+	kLockdownBlobs,
+	kDropBlobs,
+	kZapBlobs,
+	kKillBlobs,
+	kDropGrays,
+	kLosing,
+	kWinning,
+	kChooseDifficulty,
+	kWaitingToStart,
+	kIdlePlayer,
+	kWaitForRetrieval
+};
+
+#define kDropSpeed 1
+#define kFallSpeed 1
+#define kFlashSpeed 5
+
+#define kLeftPlayerWindowCenter  0.16
+#define kRightPlayerWindowCenter 0.84
--- /dev/null
+++ b/Source/prefs.cpp
@@ -1,0 +1,154 @@
+// prefs.c
+
+// NOTE THAT NONE OF THIS CODE IS ENDIAN-SAVVY.
+// PREFERENCES FILES WILL NOT TRANSFER BETWEEN PLATFORMS.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "main.h"
+#include "prefs.h"
+#include "music.h"
+#include "soundfx.h"
+#include "hiscore.h"
+#include "keyselect.h"
+
+#define kPrefsMaxSize 65536
+
+PrefList prefList[] = {
+		{ 'mod ', &musicOn,                    sizeof( MBoolean       ) },
+		{ 'sfx ', &soundOn,                    sizeof( MBoolean       ) },
+		{ 'size', registeredKey,               sizeof( registeredKey  ) },
+		{ 'keys', playerKeys,                  sizeof( playerKeys     ) },
+		{ 'high', scores,                      sizeof( scores         ) },
+		{ 'cmbx', &best,                       sizeof( best           ) },
+		{ 'user', registeredName,              sizeof( registeredName ) }
+					  };
+
+#define kPrefListSize (sizeof(prefList)/sizeof(prefList[0]))
+
+
+/* Loads the preferences from a file in the System Folder:Preferences. */
+
+void LoadPrefs( void )
+{
+	FILE *F;
+	int fileSize, count, digitsLeft;
+	unsigned char info, *infoAt, *dataAt, *fileData;
+	
+	F = fopen( QuickResourceName( "Preferences", 0, ".txt" ), "r" );
+
+	if( F != NULL )
+	{
+		fileData = (unsigned char*) calloc( 1, kPrefsMaxSize );
+		if( fileData != NULL )
+		{
+			fileSize = fread( fileData, 1, kPrefsMaxSize, F );
+			if( fileSize >= 0 )
+			{
+				for( count=0; count<kPrefListSize; count++ )
+				{
+					infoAt = FindPrefsLine( fileData, fileSize, prefList[count].itemName, prefList[count].size );
+					if( infoAt )
+					{
+						dataAt = (unsigned char*) prefList[count].itemPointer;
+						digitsLeft = prefList[count].size;
+						
+						while( digitsLeft-- )
+						{
+							info  = ((*infoAt >= 'A')? (*infoAt - 'A' + 0xA): (*infoAt - '0')) << 4;
+							infoAt++;
+							info |= ((*infoAt >= 'A')? (*infoAt - 'A' + 0xA): (*infoAt - '0'));
+							infoAt++;
+							
+							*dataAt++ = info;
+						}
+					}
+				}
+			}
+			
+			free( fileData );
+		}
+
+		fclose( F );
+	}
+}
+
+/* Finds a specific line in the prefs. */
+
+unsigned char* FindPrefsLine( unsigned char *prefsText, long prefsLength, long searchCode, long dataQuantity )
+{
+	unsigned char *prefsAt, *check, *endCheck;
+	
+	for( prefsAt = prefsText; prefsAt < (prefsText+prefsLength-3); prefsAt++ )
+	{
+		if( (prefsAt[0] == ((searchCode >> 24) & 0xFF)) &&
+		    (prefsAt[1] == ((searchCode >> 16) & 0xFF)) &&
+		    (prefsAt[2] == ((searchCode >>  8) & 0xFF)) &&
+		    (prefsAt[3] == ((searchCode      ) & 0xFF))    ) 
+		{
+			prefsAt += 6;
+			
+			// perform sizing check
+			
+			dataQuantity *= 2; // hexadecimal bytes are 2 chars
+			
+			if( ((prefsAt + dataQuantity) - prefsText) > prefsLength ) return NULL; // prefs block ended too early
+			
+			check = prefsAt;
+			endCheck = check + dataQuantity;
+			while( check < endCheck )
+			{
+				if( (*check < '0' || *check > '9') && (*check < 'A' || *check > 'F') )
+				{
+					return NULL; // incorrect size, too short
+				}
+					
+				check++;
+			}
+			
+			if( (*endCheck >= '0' && *endCheck <= '9') || (*endCheck >= 'A' && *endCheck <= 'F') )
+			{
+				return NULL; // incorrect size, too long
+			}
+			
+			return prefsAt;
+		}
+	}
+	
+	return NULL;
+}
+
+/* Saves out preferences into a file. */
+
+void SavePrefs( void )
+{
+	FILE *F;
+	short count, size;
+	unsigned char* dataAt;
+	
+	F = fopen( QuickResourceName( "Preferences", 0, ".txt" ), "w" );
+	
+	if( F != NULL )
+	{
+		for( count=0; count<kPrefListSize; count++ )
+		{
+			fprintf( F, "%c%c%c%c: ", (prefList[count].itemName >> 24) & 0xFF, 
+			                          (prefList[count].itemName >> 16) & 0xFF,
+			                          (prefList[count].itemName >>  8) & 0xFF,
+			                          (prefList[count].itemName      ) & 0xFF   );
+			
+			dataAt = (unsigned char*) prefList[count].itemPointer;
+			for( size=0; size<prefList[count].size; size++ )
+			{
+				fprintf( F, "%02X", *dataAt );
+				dataAt++;
+			}
+			
+			fputc( '\n', F );
+		}
+	}
+			
+	fclose( F );
+}
--- /dev/null
+++ b/Source/prefs.h
@@ -1,0 +1,17 @@
+// prefs.h
+
+void LoadPrefs( void );
+void SavePrefs( void );
+
+void GeneratePrefsFile( void );
+long UniqueSystem( void );
+
+unsigned char *FindPrefsLine( unsigned char *prefsText, long prefsLength, long searchCode, long dataQuantity );
+
+typedef struct
+{
+	long itemName;
+	void *itemPointer;
+	short size;
+}
+PrefList;
--- /dev/null
+++ b/Source/random.cpp
@@ -1,0 +1,110 @@
+// random.c
+
+#include <stdlib.h>
+
+#include "SDL.h"
+#include "main.h"
+#include "random.h"
+
+unsigned long randomSeed[2], pieceCount[2], grenadeTimer[2];
+int pieceMap[kBlobTypes], numPieces;
+
+
+static unsigned long internalRandomSeed = 1;
+
+static int internalRandom() // uses a generic rand() algorithm
+{
+	internalRandomSeed = internalRandomSeed * 1103515245 + 12345;
+	return ((internalRandomSeed >> 16) & 0x7FFF);
+}
+
+
+void InitRandom( int inNumPieces )
+{
+	int count, swap, swapWith;
+	
+	numPieces = inNumPieces;
+	randomSeed[0] = randomSeed[1] = SDL_GetTicks();
+	pieceCount[0] = pieceCount[1] = 0;
+	grenadeTimer[0] = grenadeTimer[1] = 40;
+	
+	for( count=0; count<kBlobTypes; count++ )
+	{
+		pieceMap[count] = count+1;
+	}
+
+	for( count=0; count<kBlobTypes; count++ )
+	{
+		swapWith = RandomBefore( kBlobTypes );
+		swap = pieceMap[swapWith];
+		pieceMap[swapWith] = pieceMap[count];
+		pieceMap[count] = swap;
+	}
+}
+
+void AddExtraPiece( void )
+{
+	numPieces++;
+}
+
+int GetPiece( int player )
+{
+	int result;
+	unsigned long realSeed;
+	
+	realSeed = internalRandomSeed;
+	
+	internalRandomSeed = randomSeed[player];
+	result = pieceMap[RandomBefore(numPieces)];
+	randomSeed[player] = internalRandomSeed;
+	
+	internalRandomSeed = realSeed;
+
+	return result;
+}
+
+int GetMagic( int player )
+{
+	int result;
+	long realSeed;
+	
+	realSeed = internalRandomSeed;
+	
+	internalRandomSeed = randomSeed[player];
+	result = (RandomBefore(19) == 0)? true: false;
+	randomSeed[player] = internalRandomSeed;
+	
+	internalRandomSeed = realSeed;
+
+	return result;
+}
+
+int GetGrenade( int player )
+{
+	pieceCount[player]++;
+	if( pieceCount[player] == grenadeTimer[player] )
+	{
+		grenadeTimer[player] += grenadeTimer[player] * 3 / 2;
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+static inline int RandomRange( double min, double max )
+{
+	const double kMinRand = 0.0;
+	const double kMaxRand = 32767.0;
+	double x;
+	
+	x = (internalRandom() - kMinRand) / (kMaxRand - kMinRand + 1.0);
+	return (int) (x * (max + 1.0 - min) + min);
+}
+
+int RandomBefore( int what )
+{	
+	return RandomRange( 0.0, what-1 );
+}
+
--- /dev/null
+++ b/Source/random.h
@@ -1,0 +1,10 @@
+// random.h
+
+void InitRandom( int numPieces );
+int GetPiece( int player );
+int RandomBefore( int what );
+int GetMagic( int player );
+int GetGrenade( int player );
+void AddExtraPiece( void );
+
+extern int pieceMap[kBlobTypes];
--- /dev/null
+++ b/Source/score.cpp
@@ -1,0 +1,156 @@
+// score.c
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include <string.h>
+
+#include "main.h"
+#include "score.h"
+#include "gworld.h"
+#include "graphics.h"
+#include "blitter.h"
+#include "hiscore.h"
+#include "gameticks.h"
+#include "level.h"
+
+SDL_Surface* scoreSurface;
+SDL_Surface* numberSurface;
+SDL_Surface* numberMaskSurface;
+
+
+MRect scoreWindowZRect, scoreWindowRect[2];
+MBoolean scoreWindowVisible[2] = {true, true};
+long roundStartScore[2], score[2], displayedScore[2], scoreTime[2];
+const char characterList[] = 
+{ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+  'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+  'U', 'V', 'W', 'X', 'Y', 'Z', '.', '0', '1', '2', 
+  '3', '4', '5', '6', '7', '8', '9', '!', '"', '#',
+  '$' };
+
+
+void InitScore( void )
+{
+	const double windowLoc[ ] = { 0.16, 0.84 };
+	SDL_Rect     sdlRect;
+	
+	scoreWindowZRect.top = scoreWindowZRect.left = 0;
+	scoreWindowZRect.bottom = 32; scoreWindowZRect.right = 144;
+	
+	scoreWindowRect[0] = scoreWindowRect[1] = scoreWindowZRect;
+	CenterRectOnScreen( &scoreWindowRect[0], windowLoc[0], 0.89 );
+	CenterRectOnScreen( &scoreWindowRect[1], windowLoc[1], 0.89 );
+	
+	scoreSurface = SDLU_InitSurface( SDLU_MRectToSDLRect( &scoreWindowZRect, &sdlRect ), 16 );
+	DrawPICTInSurface( scoreSurface, picBoard );
+	
+	numberSurface = LoadPICTAsSurface( picNumber, 16 );
+
+	numberMaskSurface = LoadPICTAsSurface( picNumberMask, 1 );
+	
+	displayedScore[0] = displayedScore[1] = 0;
+	score[0]          = score[1]          = 0;
+	scoreTime[0]      = scoreTime[1]      = 0;
+}
+
+void UpdateScore( int player )
+{
+	if( GameTickCount( ) >= scoreTime[player] )
+	{		
+		scoreTime[player] = GameTickCount() + 1;
+		
+		if( displayedScore[player] < score[player] )
+		{
+			if( (score[player] - displayedScore[player]) > 5000 )
+			{
+				displayedScore[player] += 1525;
+			}
+			else if( (score[player] - displayedScore[player]) > 1000 )
+			{
+				displayedScore[player] += 175;
+			}
+			else
+			{
+				displayedScore[player] += 25;
+			}
+			
+			if( displayedScore[player] > score[player] )
+				displayedScore[player] = score[player];
+			
+			ShowScore( player );
+		}
+	}
+}
+
+void ShowScore( int player )
+{
+	SDL_Rect   sourceSDLRect, destSDLRect;
+	MRect      myRect;
+	char       myString[256];
+	int        count;
+	
+	if( !scoreWindowVisible[player] ) return;
+	
+	if( control[player] == kNobodyControl )
+	{
+	}
+	else
+	{
+		sprintf( myString, "%d", displayedScore[player] );
+				
+		SDLU_AcquireSurface( scoreSurface );
+		
+		SDLU_BlitSurface( boardSurface[player], &scoreSurface->clip_rect,
+				 		  scoreSurface,         &scoreSurface->clip_rect   );
+		
+		myRect.top = 0;
+		myRect.left = 2;
+		myRect.bottom = kNumberVertSize;
+		myRect.right = myRect.left + kNumberHorizSize;
+		DrawCharacter( kCharacterScore,   &myRect );
+		OffsetMRect( &myRect, kNumberHorizSize, 0 );
+		DrawCharacter( kCharacterScore+1, &myRect );
+
+		myRect = scoreWindowZRect;
+		myRect.right -= 2;
+		myRect.left = myRect.right - kNumberHorizSize;
+		for( count = strlen(myString) - 1; count >= 0; count-- )
+		{
+			DrawCharacter( myString[count], &myRect );
+			OffsetMRect( &myRect, -kNumberHorizSize - 1, 0 );
+		}
+		
+		SDLU_ReleaseSurface( scoreSurface );
+		
+		SDLU_BlitFrontSurface( scoreSurface, 
+		                       SDLU_MRectToSDLRect( &scoreWindowZRect, &sourceSDLRect ),
+		                       SDLU_MRectToSDLRect( &scoreWindowRect[player], &destSDLRect ) );
+	}
+}
+
+void DrawCharacter( char which, const MRect *myRect )
+{
+	MRect   srcRect;
+	char    count, result;
+	
+	result = -1;
+	for( count = 0; count < kNumberAmount; count++ )
+	{
+		if( characterList[count] == which ) 
+		{
+			result = count;
+			break;
+		}
+	}
+	
+	if( result == -1 ) return;
+	
+	srcRect.top    = 0;
+	srcRect.left   = result * kNumberHorizSize;
+	srcRect.bottom = kNumberVertSize;
+	srcRect.right  = srcRect.left + kNumberHorizSize;
+	
+	SurfaceBlitMask(  numberSurface,  numberMaskSurface,  SDLU_GetCurrentSurface(),
+			         &srcRect,       &srcRect,            myRect );
+}
--- /dev/null
+++ b/Source/score.h
@@ -1,0 +1,25 @@
+// score.h
+
+void InitScore( void );
+void UpdateScore( int player );
+void ShowScore( int player );
+void DrawCharacter( char which, const MRect *myRect );
+
+#define kNumberHorizSize 16
+#define kNumberVertSize 32
+#define kNumberAmount 41
+
+extern MRect scoreWindowZRect, scoreWindowRect[2];
+extern MBoolean scoreWindowVisible[2];
+extern long roundStartScore[2], score[2], displayedScore[2];
+
+extern const char characterList[];
+/* 
+{ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+  'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+  'U', 'V', 'W', 'X', 'Y', 'Z', '.', '0', '1', '2', 
+  '3', '4', '5', '6', '7', '8', '9' };
+*/
+#define kCharacterZero  27
+#define kCharacterScore '!'
+#define kCharacterStage '#'
--- /dev/null
+++ b/Source/soundfx.h
@@ -1,0 +1,31 @@
+// soundfx.h
+
+void InitSound( void );
+void PlayStereo( short player, short which );
+void PlayStereoFrequency( short player, short which, short freq );
+void PlayMono( short which );
+void UpdateSound();
+
+enum
+{
+	kShift = 0,
+	kRotate,
+	kPlace,
+	kSquishy,
+	kBounce,
+	kSplop,
+	kWhistle,
+	kPause,
+	kLoss,
+	kVictory,
+	kMagic,
+	kWhomp,
+	kChime,
+	kClick,
+	kLevelUp,
+	kContinueSnd,
+	kBatsuSnd,
+	kNumSounds
+};
+
+extern MBoolean soundOn;
--- /dev/null
+++ b/Source/tutorial.cpp
@@ -1,0 +1,500 @@
+// tutorial.c
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include "main.h"
+#include "tutorial.h"
+#include "level.h"
+#include "font.h"
+#include "pause.h"
+#include "gworld.h"
+#include "graphics.h"
+#include "control.h"
+#include "blitter.h"
+#include "gameticks.h"
+#include "soundfx.h"
+#include "opponent.h"
+#include "keyselect.h"
+
+#include <string.h>
+
+AutoPattern tutorialPattern[] =
+{
+	{ kMessage,        0,   0,   "Welcome to the\nCandy Crisis\ntutorial!" },
+	{ kIdleTicks,      180, 0,   NULL },
+	{ kMessage,        0,   0,   "I'll be your guide\nthroughout the\ntutorial. Let's\nget started!" },
+	{ kRetrieve,       1,   1,   NULL },
+	{ kIdleTicks,      240, 0,   NULL },	
+	{ kMessage,        0,   0,   "When you start the\ngame, you'll find a\npair of candies\nfalling from the sky." },
+	{ kIdleTicks,      240, 0,   NULL },	
+	{ kMessage,        0,   0,   "Your goal is to\nkeep these candies\nfrom overflowing the\nboard!" },
+	{ kBlockUntilLand, 0,   0,   NULL },
+	{ kRetrieve,       2,   2,   NULL },
+	{ kIdleTicks,      60,  0,   NULL },
+	{ kMessage,        0,   0,   "You control the\ncandy by moving\nthem left and right." },
+	{ kIdleTicks,      120, 0,   NULL },
+	{ kPosition,       0,   0,   NULL },
+	{ kIdleTicks,      120, 0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kMessage,        0,   0,   "Press '~~'\nto make the pieces\nmove left." },
+	{ kRetrieve,       3,   3,   NULL },
+	{ kIdleTicks,      60,  0,   NULL },
+	{ kPosition,       5,   0,   NULL },
+	{ kIdleTicks,      90,  0,   NULL },
+	{ kMessage,        0,   0,  "Use '||' to\nmake the pieces\nmove right." },
+	{ kIdleTicks,      180, 0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kMessage,        0,   0,   "You can also\nmake the pieces\nrotate around each\nother." },
+	{ kRetrieve,       4,   3,   NULL },
+	{ kIdleTicks,      180, 0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kMessage,        0,   0,   "Press '{{'\nto make the pieces\nrotate." },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kPosition,       5,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kMessage,        0,   0,   "Also, '``'\ncauses the candy\nto drop faster." },
+	{ kIdleTicks,      180, 0,   NULL },	
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kMessage,        0,   0,   "To pause the game\nor adjust settings,\npress 'esc.'" },
+	{ kIdleTicks,      280, 0,   NULL },	
+	{ kMessage,        0,   0,   "The candy in\nthis game is made\nfrom a highly\nunstable substance!" },
+	{ kRetrieve,       2,   2,   NULL },
+	{ kIdleTicks,      200, 0,   NULL },
+	{ kMessage,        0,   0,   "So when four\npieces of the same\ncolor come into\ncontact..." },
+	{ kPosition,       1,   0,   NULL },
+	{ kIdleTicks,      180, 0,   NULL },
+	{ kMessage,        0,   0,   "... they vaporize!" },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kIdleTicks,      60,  0,   NULL },
+	{ kMessage,        0,   0,   "Let's see that\nonce again." },
+	{ kRetrieve,       1,   1,   NULL },
+	{ kIdleTicks,      120, 0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kMessage,        0,   0,   "Pop!" },
+	{ kIdleTicks,      120, 0,   NULL },	
+	{ kMessage,        0,   0,   "You can even get\nfive or more\npieces to pop\nall at the same\ntime!" },
+	{ kRetrieve,       4,   4,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kPosition,       4,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       4,   4,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kPosition,       3,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       4,   4,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kPosition,       4,   0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kMessage,        0,   0,   "Pop!\n\nTechniques like this\ncan earn lots of\nbonus points." },
+	{ kIdleTicks,      180, 0,   NULL },
+	{ kMessage,        0,   0,   "You can also pop\nmore than one color\nat once." },
+	{ kRetrieve,       5,   5,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kPosition,       4,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kRetrieve,       6,   5,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kPosition,       3,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kRetrieve,       3,   5,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      10,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      10,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kPosition,       5,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kMessage,        0,   0,   "All right!" },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kRetrieve,       0,   6,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kMessage,        0,   0,   "You can even set\nup devastating\nchain reactions..." },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kRetrieve,       0,   6,   NULL },
+	{ kIdleTicks,      90,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kRetrieve,       6,   6,   NULL },
+	{ kIdleTicks,      60,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kRetrieve,       0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kPosition,       1,   0,   NULL },
+	{ kMessage,        0,   0,   "... like this!" },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kMessage,        0,   0,   "Tricky, isn't it?" },
+	{ kRetrieve,       1,   2,   NULL },
+	{ kIdleTicks,      60,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kRetrieve,       1,   2,   NULL },
+	{ kIdleTicks,      60,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kRetrieve,       3,   1,   NULL },
+	{ kIdleTicks,      60,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kRetrieve,       3,   2,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kPosition,       3,   0,   NULL },
+	{ kMessage,        0,   0,  "Let's see one more\nexample of a chain\nreaction." },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kRetrieve,       1,   2,   NULL },
+	{ kIdleTicks,      60,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       5,   5,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kPosition,       1,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       5,   3,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      10,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      10,  0,   NULL },
+	{ kPosition,       1,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       3,   5,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kPosition,       0,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      10,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       3,   3,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kPosition,       0,   0,   NULL },
+	{ kIdleTicks,      20,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kIdleTicks,      90,  0,   NULL }, 
+	{ kMessage,        0,   0,   "There's one more\nthing you need to\nknow about..." },
+	{ kIdleTicks,      180, 0,   NULL },
+	{ kMessage,        0,   0,   "Watch out for the\nsee-through candy!" },
+	{ kIdleTicks,      60,  0,   NULL },
+	{ kPunish,         18,  0,   NULL },
+	{ kRetrieve,       1,   1,   NULL },
+	{ kIdleTicks,      120, 0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kPosition,       0,   0,   NULL },
+	{ kIdleTicks,      20,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kMessage,        0,   0,   "When your opponent\nvaporizes a group of\ncandies, they also\nsend some transparent\npieces to you!" },
+	{ kRetrieve,       1,   1,   NULL },
+	{ kIdleTicks,      90,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kPosition,       4,   0,   NULL },
+	{ kIdleTicks,      30,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       1,   1,   NULL },
+	{ kIdleTicks,      60,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      90,  0,   NULL },
+	{ kMessage,        0,   0,   "You can get rid of\nthese pieces by\nvaporizing something\nnext to them." },
+	{ kIdleTicks,      150, 0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kIdleTicks,      60,  0,   NULL },
+	{ kRetrieve,       1,   2,   NULL },
+	{ kMessage,        0,   0,   "There are also\nsome bonus items\nwhich can come\nin handy." },
+	{ kIdleTicks,      20,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kPosition,       0,   0,   NULL },
+	{ kIdleTicks,      20,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       3,   1,   NULL },
+	{ kIdleTicks,      20,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      10,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      10,  0,   NULL },
+	{ kPosition,       0,   0,   NULL },
+	{ kIdleTicks,      20,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       1,   3,   NULL },
+	{ kIdleTicks,      10,  0,   NULL },
+	{ kPosition,       1,   0,   NULL },
+	{ kIdleTicks,      15,  0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kMessage,        0,   0,   "One of these bonus\nitems is the Crazy\nCandy!" },
+	{ kRetrieve,       -1,  2,   NULL },
+	{ kIdleTicks,      15,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      10,  0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      100, 0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kMessage,        0,   0,   "The Crazy Candy\ntakes on the\ncolor of one of\nits neighbors." },
+	{ kIdleTicks,      60,  0,   NULL },
+	{ kRetrieve,       3,   4,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kPosition,       4,   0,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       1,   5,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kPosition,       1,   0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kMessage,        0,   0,   "Another useful bonus\nitem is the bomb!" },
+	{ kRetrieve,       2,   3,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kPosition,       0,   0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       0,   3,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       3,   2,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kPosition,       5,   0,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       4,   3,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kPosition,       3,   0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve,       2,   3,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kSpin,           0,   0,   NULL },
+	{ kIdleTicks,      5,   0,   NULL },
+	{ kPosition,       5,   0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },	
+	{ kRetrieve, kBombBottom, kBombTop, NULL },
+	{ kMessage,        0,   0,   "When the bomb lands\non one color\nof candy, all\npieces of that\ncolor will\nvaporize!" },
+	{ kIdleTicks,      100, 0,   NULL },
+	{ kPosition,       4,   0,   NULL },
+	{ kIdleTicks,      120, 0,   NULL },
+	{ kBlockUntilDrop, 0,   0,   NULL },
+	{ kIdleTicks,      200, 0,   NULL },
+	{ kMessage,        0,   0,   "Now you're ready\nto play Candy Crisis.\nGood luck!" },
+	{ kIdleTicks,      270, 0,   NULL },
+
+	{ kComplete,       0,   0,   NULL }
+};
+
+MRect           balloonRect = {0, 0, 190, 210};
+SkittlesFontPtr balloonFont;
+MPoint          balloonPt;
+char*           balloonChar;
+char            balloonMsg[256];
+int             balloonTime, tutorialTime;
+SDL_Surface*    balloonSurface = NULL;
+
+void InitTutorial( void )
+{
+	// Balloon font
+	balloonFont = GetFont( picBalloonFont );
+	
+	// Balloon backbuffer
+	if( balloonSurface == NULL )
+	{
+		SDL_Rect surfaceRect = { 0, 0, backdropSurface->w, backdropSurface->h }; 
+		balloonSurface = SDLU_InitSurface( &surfaceRect, 16 );
+	}
+	
+	// Set up auto pattern
+	autoPattern = tutorialPattern;	
+	tutorialTime = 0;
+}
+
+void EndTutorial( void )
+{
+	QuickFadeOut( NULL );
+	
+	showStartMenu = true;
+}
+
+static int CalculateBalloonWidth( char *message )
+{
+	int maxWidth = 40;
+	int currentWidth = 0;
+	
+	for( ;; )
+	{
+		char in = *message++;
+		
+		switch(in)
+		{
+			case 0:
+				return (currentWidth > maxWidth)? currentWidth: maxWidth;
+				
+			case '\n':
+				maxWidth = (currentWidth > maxWidth)? currentWidth: maxWidth;
+				currentWidth = 0;
+				break;
+			
+			default:
+				currentWidth += balloonFont->width[in];
+				break;
+		}
+	}
+}
+
+static int CalculateBalloonHeight( char *message )
+{
+	int lines = 2;
+	char *scan = message;
+	
+	while( *scan ) lines += (*scan++ == '\n');
+
+	return lines * 20;
+}
+
+void StopBalloon( void )
+{
+	balloonTime = 0x7FFFFFFF;
+}
+
+void StartBalloon( char *message )
+{
+	MPoint    balloonTip, balloonFill;
+	int       replace;
+	char*     match[] = { "~~", "||", "``", "{{" };
+	char*     search;
+	SDL_Rect  balloonSDLRect, balloonContentsSDLRect;
+	MRect     balloonContentsRect;
+	
+	strcpy( balloonMsg, message );
+	for( replace=0; replace<4; replace++ )
+	{
+		search = strstr( balloonMsg, match[replace] );
+		if( search )
+		{
+			char temp[256];
+			
+			search[0] = '%';
+			search[1] = 's';
+			sprintf( temp, balloonMsg, SDL_GetKeyName( playerKeys[1][replace] ) );
+			strcpy( balloonMsg, temp );
+		}
+	}
+	
+	// Erase previous balloons
+	SDLU_MRectToSDLRect( &balloonRect, &balloonSDLRect );
+	SDLU_BlitFrontSurface( backdropSurface, &balloonSDLRect, &balloonSDLRect );
+
+	// Draw empty balloon outline
+	SDLU_AcquireSurface( balloonSurface );
+
+	balloonRect.left = balloonRect.right - 25 - CalculateBalloonWidth ( balloonMsg );
+	balloonRect.top = balloonRect.bottom - 25 - CalculateBalloonHeight( balloonMsg );
+
+	SDLU_MRectToSDLRect( &balloonRect, &balloonSDLRect );
+	SDLU_BlitSurface( backdropSurface, &balloonSDLRect,
+	                  balloonSurface,  &balloonSDLRect  );
+	
+	balloonContentsRect = balloonRect;
+	balloonContentsRect.bottom -= 25;
+		
+	SurfaceGetEdges( balloonSurface, &balloonContentsRect );
+	SDL_FillRect( balloonSurface, 
+				  SDLU_MRectToSDLRect( &balloonContentsRect, &balloonContentsSDLRect ), 
+				  SDL_MapRGB( balloonSurface->format, 0xFF, 0xFF, 0xFF ) );
+	SurfaceCurveEdges( balloonSurface, &balloonContentsRect );
+	
+	balloonTip.v = balloonContentsRect.bottom - 2;
+	balloonTip.h = balloonContentsRect.right - 40;
+	balloonFill = balloonTip;
+
+	SurfaceBlitCharacter( balloonFont, '�', &balloonFill,  0,  0,  0,  0 );
+	SurfaceBlitCharacter( balloonFont, '�', &balloonTip,  31, 31, 31,  0 );
+	
+	SDLU_ReleaseSurface( balloonSurface );
+
+	// Blit empty balloon to screen
+	SDLU_MRectToSDLRect( &balloonRect, &balloonSDLRect );
+	SDLU_BlitFrontSurface( balloonSurface, &balloonSDLRect, &balloonSDLRect );
+	
+	balloonPt.h = balloonRect.left + 10;
+	balloonPt.v = balloonRect.top + 10;
+	balloonChar = balloonMsg;
+	balloonTime = GameTickCount( );
+	
+	OpponentChatter( true );
+}
+
+void UpdateBalloon( void )
+{
+	SDL_Rect balloonSDLRect;
+	
+	if( control[0] != kAutoControl ) return;
+	if( GameTickCount() < balloonTime ) return;
+	
+	if( balloonChar )
+	{
+		char in = *balloonChar++;
+				
+		switch( in )
+		{
+			case 0:
+				OpponentChatter( false );
+				balloonChar = NULL;
+				balloonTime += 120;
+				break;
+				
+			case '\n':
+				balloonPt.h = balloonRect.left + 10;
+				balloonPt.v += 20;
+				break;
+				
+			default:
+				if( balloonFont->width[in] > 0 )
+				{
+					SDLU_AcquireSurface( balloonSurface );
+					SurfaceBlitCharacter( balloonFont, in, &balloonPt, 0, 0, 0, 0 );
+					SDLU_ReleaseSurface( balloonSurface );
+					
+					SDLU_MRectToSDLRect( &balloonRect, &balloonSDLRect );
+					SDLU_BlitFrontSurface( balloonSurface, &balloonSDLRect, &balloonSDLRect );
+
+					balloonTime += 2;
+				}
+				break;			
+		}	
+	}
+	else
+	{
+		SDLU_MRectToSDLRect( &balloonRect, &balloonSDLRect );
+		SDLU_BlitFrontSurface( backdropSurface, &balloonSDLRect, &balloonSDLRect );
+		                    
+		StopBalloon();
+	}
+}
--- /dev/null
+++ b/Source/tutorial.h
@@ -1,0 +1,33 @@
+// tutorial.h
+
+typedef enum
+{
+	kMessage = 0,
+	kIdleTicks,
+	kRetrieve,
+	kPosition,
+	kSpin,
+	kBlockUntilLand,
+	kBlockUntilDrop,
+	kPunish,
+	kComplete,
+	kBlockUntilComplete
+}
+AutoCommand;
+
+typedef struct
+{
+	AutoCommand command;
+	int d1, d2;
+	char *message;
+}
+AutoPattern, *AutoPatternPtr;
+
+extern AutoPatternPtr autoPattern;
+extern int tutorialTime;
+
+void InitTutorial( void );
+void StartBalloon( char *message );
+void StopBalloon( void );
+void UpdateBalloon( void );
+void EndTutorial( void );
--- /dev/null
+++ b/Source/tweak.cpp
@@ -1,0 +1,128 @@
+// tweak.c
+
+#include <math.h>
+
+#include "main.h"
+#include "tweak.h"
+#include "gworld.h"
+#include "moving.h"
+#include "gameticks.h"
+#include "graphics.h"
+#include "players.h"
+
+long  xTweakTime[2], yTweakTime[2], rTweakTime[2];
+int yTweak[2], xTweak[2], xDirection[2], rTweak[2], rDirection[2];
+int lastShadow[2];
+int tweakOffsetX[4][11], tweakOffsetY[4][11];
+
+void InitTweak( void )
+{
+	int rTweakValues[] = { 0, 5, 10, 30, 50, 70, 90, 110, 130, 150, 170 };
+	int count, rotate;
+	double tweakRad;
+	
+	for( rotate = 0; rotate<2; rotate++ )
+	{
+		for( count=0; count<=10; count++ )
+		{
+			tweakRad = d2r( (90*rotate) - rTweakValues[count] );
+			tweakOffsetX[rotate][count] = (int) floor( 0.5 + cos( tweakRad ) * kBlobHorizSize );
+			tweakOffsetY[rotate][count] = (int) floor( 0.5 + sin( tweakRad ) * kBlobVertSize  );
+
+			tweakOffsetX[rotate+2][count] = -tweakOffsetX[rotate][count];
+			tweakOffsetY[rotate+2][count] = -tweakOffsetY[rotate][count];
+		}
+	}
+}
+
+void TweakFirstBlob( int player, MRect *first )
+{
+	int tweakValues[] = {0, -1, -2, -3, -6, -12};
+	
+	if( xTweak[player] > 0 )
+	{
+		OffsetMRect( first, xDirection[player] * tweakValues[xTweak[player]], 0 );
+	}
+
+	if( yTweak[player] > 0 )
+	{
+		OffsetMRect( first, 0, tweakValues[yTweak[player]] );
+	}
+}
+
+void TweakSecondBlob( int player, MRect *second )
+{
+	int x, y;
+	
+	CalcSecondBlobOffset( player, &x, &y );
+	OffsetMRect( second,
+				 tweakOffsetX[blobR[player]][rTweak[player]],
+				 tweakOffsetY[blobR[player]][rTweak[player]] );
+}
+
+void StartTweak( int player, int direction, int rotate, int fall )
+{
+	if( fall != 0 )
+	{
+		yTweak[player] = 3;
+		yTweakTime[player] = GameTickCount() + kTweakDelay;
+	}
+
+	if( direction != 0 )
+	{
+		xDirection[player] = direction;
+		xTweak[player] = 5;
+		xTweakTime[player] = GameTickCount() + kTweakDelay;
+	}
+	
+	if( rotate != 0 )
+	{
+		rTweak[player] = rotate * 5; 
+		rDirection[player] = rotate;
+		rTweakTime[player] = GameTickCount() + kTweakDelay;
+	}
+}
+
+void UpdateTweak( int player, int animation )
+{
+	MBoolean isXTweaked, isYTweaked, isRTweaked, isAnimTweaked = false;
+	
+	if( GameTickCount( ) >= animTime[player] )
+	{
+		isAnimTweaked = true;
+		animTime[player] += 2;
+		anim[player]++;	
+			
+		HandleMagic( player );
+	}
+
+	isXTweaked = ( (GameTickCount() >= xTweakTime[player]) && (xTweak[player] > 0) );
+	isYTweaked = ( (GameTickCount() >= yTweakTime[player]) && (yTweak[player] > 0) );
+	isRTweaked = ( (GameTickCount() >= rTweakTime[player]) && (rTweak[player] > 0) );
+	
+	if( isXTweaked || isRTweaked || isYTweaked || 
+	    isAnimTweaked || (shadowDepth[player] != lastShadow[player]) )
+	{	
+		EraseSpriteBlobs( player );
+		
+		if( isXTweaked )
+		{
+			xTweak[player]--;
+			xTweakTime[player] += kTweakDelay;
+		}
+		
+		if( isYTweaked )
+		{
+			yTweak[player]--;
+			yTweakTime[player] += kTweakDelay;
+		}
+
+		if( isRTweaked )
+		{
+			rTweak[player]--;
+			rTweakTime[player] += kTweakDelay;		
+		}
+		
+		DrawSpriteBlobs( player, animation );
+	}
+}
--- /dev/null
+++ b/Source/tweak.h
@@ -1,0 +1,11 @@
+// tweak.h
+
+void TweakFirstBlob( int player, MRect *first );
+void TweakSecondBlob( int player, MRect *second );
+void StartTweak( int player, int direction, int rotate, int fall );
+void UpdateTweak( int player, int suction );
+void InitTweak( void );
+
+#define d2r(x) ((x)*(pi/180))
+
+#define kTweakDelay 1
--- /dev/null
+++ b/Source/victory.cpp
@@ -1,0 +1,345 @@
+// victory.c
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include "main.h"
+#include "victory.h"
+#include "players.h"
+#include "gworld.h"
+#include "grays.h"
+#include "graphics.h"
+#include "soundfx.h"
+#include "score.h"
+#include "control.h"
+#include "random.h"
+#include "tweak.h"
+#include "gameticks.h"
+#include "level.h"
+#include "blitter.h"
+#include "music.h"
+#include "hiscore.h"
+#include "keyselect.h"
+#include "zap.h"
+#include "pause.h"
+#include "font.h"
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+unsigned long winTime, loseTime;
+long winStage, loseStage;
+float drop[kGridAcross], last[kGridAcross];
+SkittlesFontPtr victoryFont;
+
+void InitVictory( void )
+{
+	victoryFont = GetFont( picVictoryFont );
+}
+
+void EndRound( int player )
+{
+	int count;
+
+	loseTime = GameTickCount( );
+	loseStage = 0;
+
+	role[player] = kLosing;
+	emotions[player] = kEmotionPanic;
+
+	for( count=0; count<kGridAcross; count++ )
+	{
+		last[count] = 0.1;
+		drop[count] = 0.4 + RandomBefore(1000)/20000;
+		rowBounce[player][count] = 99;
+	}
+
+	if( player == 0 )
+	{
+		ChooseMusic( -1 );
+		PlayMono( kLoss );
+	}
+}
+
+void BeginVictory( int player )
+{
+	int count;
+	
+	endTime = GameTickCount( );
+	winTime = GameTickCount( );
+	winStage = 0;
+	
+	role[player] = kWinning;	
+	emotions[player] = kEmotionHappy;
+	
+	EraseSpriteBlobs( player );
+	
+	for( count=0; count<kGridAcross; count++ )
+	{
+		rowBounce[player][count] = 99;
+	}
+
+	if( player == 0 )
+	{
+		ChooseMusic( -1 );
+		PlayMono( kVictory );
+	}
+}
+
+void Lose( int player )
+{
+	int       gameTime = GameTickCount();
+	int       skip = 1;
+	MRect     boardRect;
+	
+	if( gameTime < loseTime )
+		return;
+	
+	if( gameTime > loseTime )
+	{
+		skip = 2;
+	}
+		
+	loseTime  += skip;
+	loseStage += skip;
+
+	if( loseStage < 120 )
+	{
+		DropLosers( player, skip );
+	}
+	else if( loseStage == 120 || loseStage == 121 )
+	{
+		loseStage = 122;
+
+		boardRect.top    = boardRect.left = 0;
+		boardRect.bottom = playerSurface[player]->h;
+		boardRect.bottom = playerSurface[player]->w;
+
+		SDLU_AcquireSurface( playerSurface[player] );
+		SurfaceDrawBoard( player, &boardRect );
+		SDLU_ReleaseSurface( playerSurface[player] );
+
+		CleanSpriteArea( player, &boardRect );
+	}
+	else if( loseStage == 240 || loseStage == 241 )
+	{
+		loseStage = 242;
+		if( players == 1 && control[player] == kPlayerControl )
+		{
+			if( --credits > 0 )
+			{
+				HandleDialog( kContinueDialog );
+			}
+			else
+			{
+				AddHiscore( score[player] );
+				ShowGameOverScreen( );
+				
+				showStartMenu = true;
+			}
+		}
+		else if( players == 2 )
+		{
+			AddHiscore( score[player] );
+		}
+	}
+}
+
+void DropLosers( int player, int skip )
+{
+	int x, y, suck;
+	int beginDrop[] = { 28, 14, 0, 7, 21, 35 };
+	float thisDrop;
+	MRect myRect;
+	
+	SDLU_AcquireSurface( playerSpriteSurface[player] );
+
+	for( x=0; x<kGridAcross; x++ )
+	{
+		if( loseStage >= beginDrop[x] )
+		{
+			thisDrop = last[x] + ( (float)(skip) * ( 0.7 + last[x] / 12.0 ) );
+			
+			CalcBlobRect( x, 0, &myRect );
+			myRect.top = (int) last[x];
+			myRect.bottom = kGridDown * kBlobVertSize;
+			SurfaceDrawBoard( player, &myRect );
+			SetUpdateRect( player, &myRect );
+
+			if( thisDrop <  (kGridDown*kBlobVertSize) )
+			{
+				myRect.top = (int) thisDrop;
+				myRect.bottom = myRect.top + kBlobVertSize;
+				
+				y=0;
+				while( myRect.top < (kGridDown*kBlobVertSize) )
+				{
+					if( grid[player][x][y] >= kFirstBlob && 
+						grid[player][x][y] <= kLastBlob )
+					{
+						suck = suction[player][x][y] & (kUpDown);
+						if( suck == kNoSuction ) suck = kDying;
+						SurfaceDrawBlob( player, &myRect, grid[player][x][y], suck, charred[player][x][y] );
+					}
+					else if( grid[player][x][y] == kGray )
+					{
+						SurfaceDrawAlpha( &myRect, kGray, kLight, kGrayNoBlink );
+					} 
+					
+					OffsetMRect( &myRect, 0, kBlobVertSize );
+					y++;
+				}
+				
+				last[x] = thisDrop;
+			}
+		}
+	}
+
+	SDLU_ReleaseSurface( playerSpriteSurface[player] );
+}
+
+void Win( int player )
+{
+	int x, y;
+	
+	if( GameTickCount() >= winTime )
+	{			
+		if( winStage < (kGridDown * kGridAcross) )
+		{
+			y = (kGridDown-1) - (winStage / kGridAcross);
+			x = (winStage % kGridAcross);
+			if( y & 2 ) x = (kGridAcross-1) - x;
+						
+			if( grid[player][x][y] == kGray )
+			{
+				suction[player][x][y] = kGrayBlink1;
+				death[player][x][y] = 0;
+				score[player] += 20;
+			}
+			else if( grid[player][x][y] >= kFirstBlob && grid[player][x][y] <= kLastBlob )
+			{
+				suction[player][x][y] = kInDeath;
+				death[player][x][y] = 0;
+				score[player] += 100;
+			}
+		}
+		else if( winStage == 140 && control[player] == kPlayerControl )
+		{
+			DrawTimerCount( player );
+		}
+		else if( winStage == 200 && control[player] == kPlayerControl )
+		{
+			DrawTimerBonus( player );
+		}
+		
+		winTime++;
+		winStage++;
+	}
+	
+	if( winStage < 140 )
+	{	
+		KillBlobs( player );
+	}
+
+	if( winStage >= 280 )
+	{
+		if( control[player] == kPlayerControl )
+		{
+			IncrementLevel( );
+			BeginRound( true );
+		}
+	}
+}
+
+void DrawTimerCount( int player )
+{
+	MRect playerRect;
+	
+	SDLU_AcquireSurface( playerSurface[player] );
+
+	{
+		MPoint dPoint  = { (kBlobVertSize * 3), 15 };		
+
+		SurfaceBlitCharacter( victoryFont, 'A', &dPoint,  31, 31, 0, 1  );
+	}	
+
+	{
+		MPoint dPoint  = { (kBlobVertSize * 4), kBlobHorizSize };
+		char seconds[20];
+		char *scan = seconds;
+		
+		sprintf( seconds, "%d", (endTime - startTime) / 60 );
+		while( *scan )
+		{
+			SurfaceBlitCharacter( zapFont, *scan++, &dPoint, 31, 31, 31, 1  );
+			dPoint.h--;
+		}
+		
+		dPoint.h += 6;
+		SurfaceBlitCharacter( zapFont, 'S', &dPoint,  31, 31, 31, 1  );
+	}
+
+	playerRect.top    = playerRect.left = 0;
+	playerRect.bottom = playerSurface[player]->h;
+	playerRect.right  = playerSurface[player]->w;
+	
+	CleanSpriteArea( player, &playerRect );
+	PlayStereo( player, kSplop );
+	
+	SDLU_ReleaseSurface( playerSurface[player] );
+}
+
+void DrawTimerBonus( int player )
+{
+	MRect playerRect;
+	int timer, bonus;
+	
+	SDLU_AcquireSurface( playerSurface[player] );
+	
+	{
+		MPoint dPoint  = { (kBlobVertSize * 6),     15     };
+
+		SurfaceBlitCharacter( victoryFont, 'B', &dPoint,  31, 31, 0, 1  );
+	}
+	
+	timer = (endTime - startTime) / 60;
+	     if( timer <= 10 ) bonus = 30000;
+	else if( timer <= 20 ) bonus = 10000;
+	else if( timer <= 30 ) bonus =  5000;
+	else if( timer <= 45 ) bonus =  4000;
+	else if( timer <= 60 ) bonus =  3000;
+	else if( timer <= 80 ) bonus =  2000;
+	else if( timer <=100 ) bonus =  1000;
+	else if( timer <=120 ) bonus =   500;
+	else                   bonus =     0;
+	
+	if( players == 1 ) bonus *= level;
+	
+	score[player] += bonus;
+	
+	{
+		MPoint dPoint  = { (kBlobVertSize * 7), kBlobHorizSize };
+		char points[20];
+		char *scan = points;
+		
+		sprintf( points, "%d", bonus );
+		while( *scan )
+		{
+			SurfaceBlitCharacter( zapFont, *scan++, &dPoint, 31, 31, 31, 1  );
+			dPoint.h--;
+		}
+		
+		dPoint.h += 6;
+		SurfaceBlitCharacter( zapFont, 'P', &dPoint,  31, 31, 31, 1  );
+	}
+	
+	playerRect.top    = playerRect.left = 0;
+	playerRect.bottom = playerSurface[player]->h;
+	playerRect.right  = playerSurface[player]->w;
+
+	CleanSpriteArea( player, &playerRect );
+	PlayStereo( player, kSplop );
+	
+	SDLU_ReleaseSurface( playerSurface[player] );
+}
--- /dev/null
+++ b/Source/victory.h
@@ -1,0 +1,14 @@
+// victory.h
+
+void EndRound( int player );
+void BeginVictory( int player );
+void InitVictory( void );
+void Lose( int player );
+void Win( int player );
+void DropLosers( int player, int skip );
+void DrawTimerCount( int player );
+void DrawTimerBonus( int player );
+
+#define kNumLetters 11
+#define kLetterHorizSize 13
+#define kLetterVertSize 20
--- /dev/null
+++ b/Source/zap.cpp
@@ -1,0 +1,618 @@
+// zap.c
+
+#include "SDL.h"
+#include "SDLU.h"
+
+#include <stdio.h>
+
+#include "main.h"
+#include "players.h"
+#include "zap.h"
+#include "grays.h"
+#include "soundfx.h"
+#include "gworld.h"
+#include "graphics.h"
+#include "gameticks.h"
+#include "level.h"
+#include "random.h"
+#include "tweak.h"
+#include "blitter.h"
+#include "font.h"
+#include "score.h"
+#include "hiscore.h"
+
+#include <stdlib.h>
+#include <math.h>
+#include <stdio.h>
+
+signed char death[2][kGridAcross][kGridDown];
+int zapIteration[2];
+int grenadeFrame[2] = {kBlastFrames + 1, kBlastFrames + 1}, zapScoreFrame[2];
+MPoint zapScorePt[2];
+MRect grenadeRect[2];
+SkittlesFontPtr zapFont, zapOutline;
+char zapScore[2][20] = { "", "" };
+int zapScoreWidth[2];
+int zapScoreR[2], zapScoreG[2], zapScoreB[2];
+int zapOffsetX[7][kZapFrames], zapOffsetY[7][kZapFrames];
+
+#define min(x,y) (((x)<(y))?(x):(y))
+#define arrsize(x) (sizeof(x)/sizeof(x[0]))
+
+void ZapScoreDisplay( int player, int amount, int multiplier, int x, int y, int c )
+{
+	char *scan;
+
+	if( amount     > 0 && 
+	    multiplier > 0 && 
+	    x >= 0 && x < kGridAcross &&
+	    y >= 0 && y < kGridDown   &&
+	    c >= kFirstBlob && c <= (kLastBlob+1) )
+	{
+		zapScorePt[player].v = y * kBlobVertSize  + 6;
+		zapScorePt[player].h = x * kBlobHorizSize + 6;
+
+		zapScoreR[player] = glowColors[c][0];
+		zapScoreG[player] = glowColors[c][1];
+		zapScoreB[player] = glowColors[c][2];
+
+	    sprintf( zapScore[player], (multiplier == 1)? "%d": "%d*%d", amount, multiplier );
+
+		zapScoreWidth[player] = 0;
+		scan = zapScore[player];
+		while( *scan ) zapScoreWidth[player] += zapFont->width[*scan++];
+		
+		if( (zapScorePt[player].h + zapScoreWidth[player] + 8) > (kGridAcross * kBlobHorizSize) )
+		{
+			zapScorePt[player].h = (kGridAcross * kBlobHorizSize) - zapScoreWidth[player] - 8;
+		}
+	}
+}
+
+void ZapBlobs( int player )
+{
+	int x, y, cluster, clusterCount = 0, multiplier, amount = 0;
+	int zapFocusX = -1, zapFocusY = -1, zapFocusC = 0;
+	
+	
+	zapScorePt[player].v = 0;
+	zapScoreFrame[player] = 0;
+	
+	switch( chain[player] )
+	{
+		case 1:  multiplier = 1;                  break;
+		default: multiplier = 2 << chain[player]; break;
+	}
+
+	for( y=kGridDown-1; y>=0; y-- )
+	{
+		for( x=kGridAcross-1; x>=0; x-- )
+		{
+			if( grid[player][x][y] >= kFirstBlob &&
+				grid[player][x][y] <= kLastBlob &&
+				suction[player][x][y] != kInDeath )
+			{
+				cluster = SizeUp( grid[player], x, y, grid[player][x][y] );
+				if( cluster >= kBlobClusterSize )
+				{
+					clusterCount++;
+					zapFocusX = x;
+					zapFocusY = y;
+					zapFocusC = grid[player][x][y];
+					
+					amount += cluster * 10;
+					
+					multiplier += cluster - kBlobClusterSize;
+					
+					RemoveBlobs( player, x, y, grid[player][x][y], 0 );
+				}
+			}
+		}
+	}
+
+	if( clusterCount > 0 )
+	{
+		switch( clusterCount )
+		{
+			case 1:                     break;
+			case 2:   multiplier += 3;  break;
+			case 3:   multiplier += 6;  break;
+			case 4:   multiplier += 12; break;
+			default:  multiplier += 24; break;
+		}
+
+		if( multiplier > 999 ) multiplier = 999;
+		CalculateGrays( 1-player, amount * multiplier / difficulty[player] );
+		potentialCombo[player].value += amount * multiplier;
+		
+		if( players == 1 ) amount *= ((level <= kLevels)? level: 1);
+		score[player] += amount * multiplier;
+		
+		ZapScoreDisplay( player, amount, multiplier, zapFocusX, zapFocusY, zapFocusC );
+	}
+	
+	blobTime[player] = GameTickCount( );
+	
+	if( clusterCount > 0 )
+	{
+		chain[player]++;
+		role[player] = kKillBlobs;
+		PlayStereoFrequency( player, kSquishy, zapIteration[player]++ );
+	}
+	else
+	{
+		if( control[player] == kPlayerControl )
+		{
+			SubmitCombo( &potentialCombo[player] );
+		}
+		
+		SetupGrays( player );
+		role[player] = kDropGrays;
+		
+		if( BusyDroppingGrays( player ) )
+		{
+			PlayStereoFrequency( player, kWhistle, player );
+		}
+	}
+}
+
+void RemoveBlobs( int player, int x, int y, int color, int generation )
+{
+	if( (x<0) || (x>=kGridAcross) || (y<0) || (y>=kGridDown) )
+		return;
+	
+	if( grid[player][x][y] == kGray )
+	{
+		suction[player][x][y] = kGrayBlink1;
+		death[player][x][y] = -8 - generation;
+		return;
+	}
+	
+	if( grid[player][x][y] != color || suction[player][x][y] == kInDeath )
+		return;
+	
+	suction[player][x][y] = kInDeath;
+	death[player][x][y] = -12 - generation;
+	
+	RemoveBlobs( player, x-1, y,   color, generation+3 );
+	RemoveBlobs( player, x+1, y,   color, generation+3 );
+	RemoveBlobs( player, x,   y-1, color, generation+3 );
+	RemoveBlobs( player, x,   y+1, color, generation+3 );
+}
+
+void KillBlobs( int player )
+{
+	int x,y;
+	const int   position[] = { 0, 15, 27, 39, 51, 63, 72, 81, 90, 99, 105,111,117,123,126,129,131,132,133,134,135,135,136,136,137,137,138,138,138,139,139,139,139,140,140,140 };
+	const int   shading [] = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 30, 29, 28, 26, 24, 21, 18, 15, 12, 9,  6,  3,  0   };
+	const int   blobGraphic[kZapFrames] = { kDying,   kDying,   kDying,   kDying,   kSquish1, 
+								            kSquish1, kSquish1, kSquish1, kSquish2, kSquish2, 
+			 					            kSquish2, kSquish2, kSquish3, kSquish3, kSquish3,
+								            kSquish3, kSquish4, kSquish4, kSquish4, kSquish4 },
+		  		grayGraphic[kZapFrames] = { kGrayBlink1, kGrayBlink1, kGrayBlink1,
+		  						            kGrayBlink1, kGrayBlink1, kGrayBlink1, 
+		  						            kGrayBlink2, kGrayBlink2, kGrayBlink2,
+		  						            kGrayBlink2, kGrayBlink2, kGrayBlink2, 
+		  						            kGrayBlink3, kGrayBlink3, kGrayBlink3,
+		  					            	kGrayBlink3, kGrayBlink3, kGrayBlink3, 
+		  						            kGrayBlink3, kGrayBlink3 };
+	MRect myRect;
+	MBoolean busy = false;
+	MPoint dPoint, oPoint;
+	char *scan;
+	
+	if( blobTime[player] > GameTickCount( ) )
+		return;
+	
+	blobTime[player]++;
+
+	SDLU_AcquireSurface( playerSurface[player] );
+	
+	// clear grenade sprite
+	if( grenadeFrame[player] <= kBlastFrames )
+	{
+		CleanSpriteArea( player, &grenadeRect[player] );
+		if( grenadeFrame[player] == kBlastFrames ) grenadeFrame[player]++;
+	}
+		
+	for( x=0; x<kGridAcross; x++ )
+	{
+		for( y=0; y<kGridDown; y++ )
+		{
+			if( grid[player][x][y] >= kFirstBlob &&  // if a blob is dying
+				grid[player][x][y] <= kLastBlob &&
+				suction[player][x][y] == kInDeath )
+			{
+				death[player][x][y]++;
+				busy = true;
+				
+				CalcBlobRect( x, y, &myRect );
+				
+				if( death[player][x][y] >= 0 && death[player][x][y] <= kZapFrames ) // draw its death
+				{					
+					if( death[player][x][y] == kZapFrames )
+					{
+						grid[player][x][y] = kEmpty;
+						suction[player][x][y] = kNoSuction;
+						charred[player][x][y] = kNoCharring;
+						SurfaceDrawBlob( player, &myRect, kEmpty, kNoSuction, kNoCharring );
+						CleanSpriteArea( player, &myRect );
+					}
+					else
+					{
+						SurfaceDrawBlob( player, &myRect,
+								         grid[player][x][y],
+								         blobGraphic[ death[player][x][y] ],
+								         kNoCharring );
+						CleanSpriteArea( player, &myRect );
+					}
+					
+					CleanChunks( player, x, y, death[player][x][y], character[player].zapStyle );
+				}
+				else
+				{
+					SurfaceDrawBlob( player, &myRect, grid[player][x][y],
+								(blobTime[player] & 2)? kFlashDarkBlob: kNoSuction, kNoCharring );
+					CleanSpriteArea( player, &myRect );
+				}
+			}
+			else
+			{
+				if( grid[player][x][y] == kGray &&					// gray dying
+					suction[player][x][y] == kGrayBlink1 )
+				{
+					CalcBlobRect( x, y, &myRect );
+					
+					if( death[player][x][y] >= 0 && death[player][x][y] <= kZapFrames )
+					{
+						if( death[player][x][y] == kZapFrames )
+						{
+							grid[player][x][y] = kEmpty;
+							suction[player][x][y] = kNoSuction;
+							SurfaceDrawBlob( player, &myRect, kEmpty, kNoSuction, kNoCharring );
+						}
+						else
+						{
+							SurfaceDrawBoard( player, &myRect );
+							SurfaceDrawAlpha( &myRect, kGray, kLight, grayGraphic[ death[player][x][y] ] );
+							busy = true;
+						}
+						CleanSpriteArea( player, &myRect );
+					}
+					
+					death[player][x][y]++;
+				}
+			}
+		}
+	}
+	
+	// draw score info above blobs but below chunks and explosions
+	
+	if( zapScoreFrame[player] < arrsize(position) )
+	{
+		myRect.top    = zapScorePt[player].v - (position[zapScoreFrame[player]    ]);
+		myRect.left   = zapScorePt[player].h;
+		myRect.bottom = zapScorePt[player].v - (position[zapScoreFrame[player] - 1]) + 15;
+		myRect.right  = myRect.left + zapScoreWidth[player];
+		CleanSpriteArea( player, &myRect );
+
+		if( zapScoreFrame[player] < arrsize(position)-1 )
+		{		
+			SDLU_AcquireSurface( playerSpriteSurface[player] );	
+			
+			dPoint.v = oPoint.v = myRect.top;
+			dPoint.h = oPoint.h = myRect.left;
+			scan = zapScore[player];
+			while( *scan )
+			{
+				SurfaceBlitWeightedCharacter( zapFont,    *scan, &dPoint, zapScoreR[player], zapScoreG[player], zapScoreB[player], shading[zapScoreFrame[player]] );
+				SurfaceBlitWeightedCharacter( zapOutline, *scan, &oPoint, 0,                 0,                 0,                 shading[zapScoreFrame[player]] );
+				scan++;
+			}
+			
+			SDLU_ReleaseSurface( playerSpriteSurface[player] );	
+
+			zapScoreFrame[player]++;
+			busy = true;
+		}	
+	}
+		
+	///////////////////////////////////////////////////////////////
+
+	for( x=0; x<kGridAcross; x++ )
+	{
+		for( y=0; y<kGridDown; y++ )
+		{
+			if( grid[player][x][y] >= kFirstBlob &&  // if a blob is dying
+				grid[player][x][y] <= kLastBlob &&
+				suction[player][x][y] == kInDeath &&
+				death[player][x][y] >= 0 && death[player][x][y] < kZapFrames ) // draw chunks (after all that stuff)
+			{
+				DrawChunks( player, x, y, death[player][x][y], character[player].zapStyle );
+			}
+		}
+	}
+	
+	SDLU_ReleaseSurface( playerSurface[player] );
+	
+	if( grenadeFrame[player] < kBlastFrames )
+	{
+		busy = true;
+		
+		SDLU_AcquireSurface( playerSpriteSurface[player] );
+		
+		myRect.top = grenadeFrame[player] * kBlastHeight;
+		myRect.left = 0;
+		myRect.bottom = myRect.top + kBlastHeight;
+		myRect.right = kBlastWidth;
+		
+		SurfaceBlitAlpha(  playerSpriteSurface[player],  blastSurface,  blastMaskSurface,  playerSpriteSurface[player],
+						  &grenadeRect[player],         &myRect,       &myRect,           &grenadeRect[player]          );
+
+		grenadeFrame[player]++;
+
+		SDLU_ReleaseSurface( playerSpriteSurface[player] );
+	}
+	
+	if( !busy && role[player] == kKillBlobs )
+	{
+		blobTime[player] = GameTickCount( );
+		halfway[player] = false;
+		role[player] = kDropBlobs;
+	}
+}
+
+int SizeUp( signed char myGrid[kGridAcross][kGridDown], int x, int y, int color )
+{	
+	int total;
+	
+	total = GetChainSize( myGrid, x, y, color );
+	CleanSize( myGrid, x, y, color );
+	
+	return total;
+}
+
+int GetChainSize( signed char myGrid[kGridAcross][kGridDown], int x, int y, int color )
+{
+	int total;
+	
+	if( (x<0) || (x>=kGridAcross) || (y<0) || (y>=kGridDown) ) return 0;
+	if( myGrid[x][y] != color ) return 0;
+
+	myGrid[x][y] = -color;
+	
+	total = 1 + GetChainSize( myGrid, x-1, y, color )
+			  + GetChainSize( myGrid, x+1, y, color )
+			  + GetChainSize( myGrid, x, y-1, color )
+			  + GetChainSize( myGrid, x, y+1, color );
+	
+	return total;
+}
+
+void CleanWithPolish( signed char myGrid[kGridAcross][kGridDown], signed char polish[kGridAcross][kGridDown], int x, int y, int color )
+{
+	if( (x<0) || (x>=kGridAcross) || (y<0) || (y>=kGridDown) ) return;
+	
+	if( myGrid[x][y] == -color )
+	{
+		myGrid[x][y] = color;
+		polish[x][y] = true;
+		
+		CleanWithPolish( myGrid, polish, x-1, y, color );
+		CleanWithPolish( myGrid, polish, x+1, y, color );
+		CleanWithPolish( myGrid, polish, x, y-1, color );
+		CleanWithPolish( myGrid, polish, x, y+1, color );
+	}
+}
+
+void CleanSize( signed char myGrid[kGridAcross][kGridDown], int x, int y, int color )
+{
+	if( (x<0) || (x>=kGridAcross) || (y<0) || (y>=kGridDown) ) return;
+	
+	if( myGrid[x][y] == -color )
+	{
+		myGrid[x][y] = color;
+		
+		CleanSize( myGrid, x-1, y, color );
+		CleanSize( myGrid, x+1, y, color );
+		CleanSize( myGrid, x, y-1, color );
+		CleanSize( myGrid, x, y+1, color );
+	}
+}
+
+void CleanChunks( int player, int x, int y, int level, int style )
+{
+	int count, color, type;
+	MRect chunkRect;
+	
+	if( flashyAnimation )
+	{
+		SDLU_AcquireSurface( playerSpriteSurface[player] );
+		
+		for( count=-3; count<=3; count++ )
+		{
+			if( count != 0 )
+			{
+				if( level > 0 )
+				{
+					CalcBlobRect( x, y, &chunkRect );
+					GetZapStyle( player, &chunkRect, &color, &type, count, level-1, style );
+					CleanSpriteArea( player, &chunkRect );
+				}
+				
+				if( level < kZapFrames )
+				{
+					CalcBlobRect( x, y, &chunkRect );
+					GetZapStyle( player, &chunkRect, &color, &type, count, level, style );
+					CleanSpriteArea( player, &chunkRect );
+				}
+			}
+		}
+		
+		SDLU_ReleaseSurface( playerSpriteSurface[player] );
+	}
+}
+
+void DrawChunks( int player, int x, int y, int level, int style )
+{
+	int count, color, type;
+	MRect chunkRect;
+	
+	if( flashyAnimation )
+	{
+		SDLU_AcquireSurface( playerSpriteSurface[player] );
+		
+		for( count=-3; count<=3; count++ )
+		{
+			if( count != 0 )
+			{
+				CalcBlobRect( x, y, &chunkRect );
+				color = grid[player][x][y];
+				GetZapStyle( player, &chunkRect, &color, &type, count, level, style );
+				SurfaceDrawSprite( &chunkRect, color, type );
+			}
+		}
+		
+		SDLU_ReleaseSurface( playerSpriteSurface[player] );
+	}
+}
+
+void CleanSplat( int player, int x, int y, int level )
+{
+	int count, color, type;
+	MRect chunkRect;
+
+	if( flashyAnimation )
+	{
+		SDLU_AcquireSurface( playerSpriteSurface[player] );
+		
+		for( count=-2; count<=2; count++ )
+		{
+			if( count != 0 )
+			{
+				if( level > 0 )
+				{
+					CalcBlobRect( x, y, &chunkRect );
+					GetZapStyle( player, &chunkRect, &color, &type, count, level-1, 4 );
+					CleanSpriteArea( player, &chunkRect );
+				}
+				
+				if( level < kZapFrames )
+				{
+					CalcBlobRect( x, y, &chunkRect );
+					GetZapStyle( player, &chunkRect, &color, &type, count, level, 4 );
+					CleanSpriteArea( player, &chunkRect );
+				}
+			}
+		}
+		
+		SDLU_ReleaseSurface( playerSpriteSurface[player] );
+	}
+}
+
+void DrawSplat( int player, int x, int y, int level )
+{
+	int count, color = kGray, type;
+	MRect chunkRect;
+	
+	if( flashyAnimation )
+	{
+		SDLU_AcquireSurface( playerSpriteSurface[player] );
+		
+		for( count=-2; count<=2; count++ )
+		{
+			if( level < kZapFrames && count != 0 )
+			{
+				CalcBlobRect( x, y, &chunkRect );
+				GetZapStyle( player, &chunkRect, &color, &type, count, level, 4 );
+				SurfaceDrawAlpha( &chunkRect, kGray, kLight, type );
+			}
+		}
+		
+		SDLU_ReleaseSurface( playerSpriteSurface[player] );
+	}
+}
+
+void InitZapStyle( void )
+{
+	int count, which;
+	double x;
+	const double position[kZapFrames] = {0, 10, 20, 28, 35, 42, 48, 54, 60, 64, 68, 70, 72, 73, 73, 74, 74, 75, 75, 75};
+	const double offset[7]   = {-30, -50, -70, 0, -110, -130, -150};
+	
+	zapFont    = GetFont( picZapFont );
+	zapOutline = GetFont( picZapOutlineFont );
+	
+	for( count=0; count<kZapFrames; count++ )
+	{
+		for( which=0; which<7; which++ )
+		{
+			x = d2r(offset[which]);
+				
+			zapOffsetX[which][count] = (int) floor( 0.5 + position[count] * cos( x ) );
+			zapOffsetY[which][count] = (int) floor( 0.5 + position[count] * sin( x ) );
+		}
+	}
+}
+
+
+void GetZapStyle( int player, MRect *myRect, int *color, int *type, int which, int level, int style )
+{
+	const int chunkGraphic[] = { kSquish1, kSquish1, kSquish1, kSquish1, kSquish1, 
+								 kSquish2, kSquish2, kSquish2, kSquish2, kSquish2,
+								 kSquish3, kSquish3, kSquish3, kSquish3, kSquish3,
+								 kSquish4, kSquish4, kSquish4, kSquish4, kSquish4 };
+	
+	color; // later
+	*type = chunkGraphic[level];
+	
+	switch(  style )
+	{
+		case 0:
+		{
+			const int direction[7][2] = { {0, -2}, {-2,-1}, {-2,1}, {0,0}, {2,-1}, {2,1}, {0, 2} };
+			const int position[kZapFrames] = {0, 5, 9, 13, 17, 21, 24, 26, 30, 33, 35, 37, 39, 41, 42, 43, 43, 44, 44, 44 };
+			const int yVelocity = 2;
+			
+			OffsetMRect( myRect, direction[which+3][0] * position[level],
+								direction[which+3][1] * position[level] );
+			break;
+		}
+
+				
+		case 1:
+		{
+			const int xVelocity = 3;
+			const int yOffset[3][kZapFrames]   = {  { -4, -8, -12, -17, -22, -26, -30, -33, -36, -39, -41, -42, -43, -44, -44, -44, -43, -42, -41, -39 },
+											        { -4, -7, -10, -14, -18, -21, -23, -25, -26, -27, -27, -27, -26, -25, -23, -21, -18, -14, -10, -7 },
+											        { -2, -4, -5, -6, -7, -8, -9, -10, -10, -11, -11, -11, -10, -9, -8, -7, -6, -5, -3, -1 } };
+
+			OffsetMRect( myRect, xVelocity * level * which, yOffset[abs(which)-1][level] );
+			
+			break;
+		}
+		
+		case 2:
+		{
+			const int position[kZapFrames] = {0, 5, 9, 13, 17, 21, 24, 27, 30, 33, 35, 37, 39, 41, 42, 43, 43, 44, 44, 44 };
+			
+			OffsetMRect( myRect, 0, position[level] * which );
+			break;
+		}
+		
+		case 3:
+		{
+			double fLevel = ((double)level) / 2;
+			OffsetMRect( myRect, (int)((player? -1.0: 1.0) * abs(which) * fLevel * (fLevel-1)), (int)((which-3) * fLevel) );
+			break;
+		}
+		
+		case 4:
+		{
+			OffsetMRect( myRect, zapOffsetX[which+3][level],
+							 	 zapOffsetY[which+3][level] );
+			
+			break;
+		}
+	}
+}
--- /dev/null
+++ b/Source/zap.h
@@ -1,0 +1,30 @@
+// zap.h
+
+#include "font.h"
+
+void ZapScoreDisplay( int player, int amount, int multiplier, int x, int y, int c );
+int SizeUp( signed char myGrid[kGridAcross][kGridDown], int x, int y, int color );
+int GetChainSize( signed char myGrid[kGridAcross][kGridDown], int x, int y, int color );
+void CleanSize( signed char myGrid[kGridAcross][kGridDown], int x, int y, int color );
+void CleanWithPolish( signed char myGrid[kGridAcross][kGridDown], signed char polish[kGridAcross][kGridDown], int x, int y, int color );
+void RemoveBlobs( int player, int x, int y, int color, int generation );
+void KillBlobs( int player );
+void ZapBlobs( int player );
+void CleanChunks( int player, int x, int y, int level, int style );
+void DrawChunks( int player, int x, int y, int level, int style );
+void CleanSplat( int player, int x, int y, int level );
+void DrawSplat( int player, int x, int y, int level );
+void GetZapStyle( int player, MRect *myRect, int *color, int *type, int which,
+				  int level, int style );
+void InitZapStyle( void );
+
+extern signed char death[2][kGridAcross][kGridDown];
+extern int zapIteration[2];
+
+extern int grenadeFrame[2];
+extern MRect grenadeRect[2];
+
+extern SkittlesFontPtr zapFont, zapOutline;
+
+#define kBlobClusterSize 4
+#define kZapFrames 20
binary files /dev/null b/fmod.dll differ
binary files /dev/null b/jpeg.dll differ
binary files /dev/null b/libpng1.dll differ
binary files /dev/null b/zlib.dll differ