[Tux4kids-commits] r887 - in tuxmath/trunk: . src

dbruce-guest at alioth.debian.org dbruce-guest at alioth.debian.org
Sun Feb 15 16:45:11 UTC 2009


Author: dbruce-guest
Date: 2009-02-15 16:45:10 +0000 (Sun, 15 Feb 2009)
New Revision: 887

Added:
   tuxmath/trunk/src/
   tuxmath/trunk/src/CMakeLists.txt
   tuxmath/trunk/src/Makefile.am
   tuxmath/trunk/src/Makefile.in
   tuxmath/trunk/src/SDL_extras.c
   tuxmath/trunk/src/SDL_extras.h
   tuxmath/trunk/src/SDL_rotozoom.c
   tuxmath/trunk/src/SDL_rotozoom.h
   tuxmath/trunk/src/audio.c
   tuxmath/trunk/src/campaign.c
   tuxmath/trunk/src/campaign.h
   tuxmath/trunk/src/compiler.h
   tuxmath/trunk/src/convert_utf.c
   tuxmath/trunk/src/convert_utf.h
   tuxmath/trunk/src/credits.c
   tuxmath/trunk/src/credits.h
   tuxmath/trunk/src/exercise_mathcards
   tuxmath/trunk/src/exercise_mathcards.c
   tuxmath/trunk/src/factoroids.c
   tuxmath/trunk/src/factoroids.h
   tuxmath/trunk/src/fileops.c
   tuxmath/trunk/src/fileops.h
   tuxmath/trunk/src/fileops_media.c
   tuxmath/trunk/src/game.c
   tuxmath/trunk/src/game.h
   tuxmath/trunk/src/generate_lesson.c
   tuxmath/trunk/src/gettext.h
   tuxmath/trunk/src/globals.h
   tuxmath/trunk/src/highscore.c
   tuxmath/trunk/src/highscore.h
   tuxmath/trunk/src/lessons.c
   tuxmath/trunk/src/lessons.h
   tuxmath/trunk/src/linewrap.c
   tuxmath/trunk/src/linewrap.h
   tuxmath/trunk/src/loaders.c
   tuxmath/trunk/src/loaders.h
   tuxmath/trunk/src/mathcards.c
   tuxmath/trunk/src/mathcards.h
   tuxmath/trunk/src/multiplayer.c
   tuxmath/trunk/src/multiplayer.h
   tuxmath/trunk/src/options.c
   tuxmath/trunk/src/options.h
   tuxmath/trunk/src/pixels.c
   tuxmath/trunk/src/pixels.h
   tuxmath/trunk/src/scandir.c
   tuxmath/trunk/src/scandir.h
   tuxmath/trunk/src/setup.c
   tuxmath/trunk/src/setup.h
   tuxmath/trunk/src/svn-commit.tmp
   tuxmath/trunk/src/titlescreen.c
   tuxmath/trunk/src/titlescreen.h
   tuxmath/trunk/src/tuxmath.c
   tuxmath/trunk/src/tuxmath.h
   tuxmath/trunk/src/tuxmathadmin.c
   tuxmath/trunk/src/tuxmathrc.rc
Log:
updated src



Added: tuxmath/trunk/src/CMakeLists.txt
===================================================================
--- tuxmath/trunk/src/CMakeLists.txt	                        (rev 0)
+++ tuxmath/trunk/src/CMakeLists.txt	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,145 @@
+#The following isn't necessary because of the SDL workaround
+#cmake_minimum_required(VERSION 2.4.7 FATAL_ERROR)
+
+## Libraries
+find_package(SDL REQUIRED)
+find_package(SDL_image REQUIRED)
+find_package(SDL_ttf REQUIRED)
+find_package(SDL_mixer REQUIRED)
+find_package(SDL_gfx)
+
+if (NOT SDLGFX_FOUND)
+  message("Adding rotozoom")
+  set(TUXMATH_EXTRA_SRC ${TUXMATH_EXTRA_SRC} SDL_rotozoom.c)
+endif (NOT SDLGFX_FOUND)
+
+
+## Define the source files used for each executable
+# tuxmath
+set(SOURCES_TUXMATH
+  audio.c
+  ConvertUTF.c
+  credits.c
+  factoroids.c
+  fileops.c
+  fileops_media.c
+  game.c
+  highscore.c
+  lessons.c
+  loaders.c
+  mathcards.c
+  options.c
+  pixels.c
+  scandir.c
+  SDL_extras.c
+  setup.c
+  titlescreen.c
+  multiplayer.c
+  campaign.c
+  tuxmath.c
+  linewrap.c
+  ${TUXMATH_EXTRA_SRC}
+  )
+
+message("${SOURCES_TUXMATH}")
+
+# tuxmathadmin
+set(SOURCES_TUXMATHADMIN
+  tuxmathadmin.c
+  )
+
+if (NOT SDL_FOUND)
+  # Workaround for REQUIRED flag not working with cmake < 2.4.7.
+  # Should put other libraries in, too.
+  message(FATAL_ERROR "SDL not found!")
+endif (NOT SDL_FOUND)
+
+if (NOT APPLE)
+  link_libraries (SDLmain)
+endif (NOT APPLE)
+
+## Include files
+if (NOT BUILD_INTL)
+  # Generate the config.h file
+  configure_file(${TuxMath_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h)
+endif (NOT BUILD_INTL)
+check_include_file(${CMAKE_CURRENT_BINARY_DIR}/config.h HAVE_CONFIG_H)
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR} ${SDL_INCLUDE_DIR} ${SDLIMAGE_INCLUDE_DIR} ${SDLMIXER_INCLUDE_DIR} ${SDLTTF_INCLUDE_DIR} ${TUXMATH_EXTRA_INCLUDES} ${INTL_BINARY_DIR})
+if (SDLGFX_FOUND)
+   include_directories(${SDLGFX_INCLUDE_DIR})
+endif (SDLGFX_FOUND)
+
+if (TUXMATH_BUILD_INTL)
+  link_directories(${INTL_BINARY_DIR})
+endif(TUXMATH_BUILD_INTL)
+
+if (TUXMATH_BUILD_LINEBREAK)
+  link_directories(${LINEBREAK_BINARY_DIR})
+endif (TUXMATH_BUILD_LINEBREAK)
+
+## Define the executables
+add_executable (
+  tuxmath
+  MACOSX_BUNDLE
+  WIN32
+  ${SOURCES_TUXMATH}
+  )
+
+add_executable (
+  tuxmathadmin
+  ${SOURCES_TUXMATHADMIN}
+  )
+
+set_target_properties (
+  tuxmath
+  PROPERTIES COMPILE_FLAGS 
+  "-DDATA_PREFIX=\\\"${TUXMATH_DATA_PREFIX}\\\" -DVERSION=\\\"${TUXMATH_VERSION}\\\" -DLOCALEDIR=\\\"${LOCALE_DIR}\\\" -DPACKAGE=\\\"tuxmath\\\""
+  )
+
+target_link_libraries (tuxmath
+  ${SDL_LIBRARY}
+  ${SDLIMAGE_LIBRARY}
+  ${SDLTTF_LIBRARY}
+  ${SDLMIXER_LIBRARY}
+  )
+
+if (SDLPANGO_FOUND)
+  target_link_libraries (tuxmath
+    ${SDLPANGO_LIBRARY}
+    )
+endif (SDLPANGO_FOUND)
+
+if (SDLGFX_FOUND)
+   target_link_libraries (tuxmath
+   ${SDLGFX_LIBRARY}
+   )
+endif (SDLGFX_FOUND)
+
+if (APPLE)
+  # The following seems to be needed to compile under 10.5
+  set_target_properties(tuxmath tuxmathadmin
+    PROPERTIES
+    LINK_FLAGS "-mmacosx-version-min=10.4")
+endif(APPLE)
+
+if (TUXMATH_BUILD_INTL)
+  message("Linking iconv: ${ICONV_LIBRARIES}")
+  target_link_libraries(tuxmath ${ICONV_LIBRARIES} libintl.a)
+endif(TUXMATH_BUILD_INTL)
+
+if (TUXMATH_BUILD_LINEBREAK)
+  target_link_libraries(tuxmath ${ICONV_LIBRARIES} liblinebreak.a)
+endif(TUXMATH_BUILD_LINEBREAK)
+
+set_target_properties (
+  tuxmathadmin
+  PROPERTIES COMPILE_FLAGS 
+  "-DDATA_PREFIX=\\\"${TUXMATH_DATA_PREFIX}\\\" -DVERSION=\\\"${TUXMATHADMIN_VERSION}\\\" -DLOCALEDIR=\\\"${LOCALE_DIR}\\\" -DPACKAGE=\\\"tuxmathadmin\\\""
+  )
+
+## Installation specifications
+if (UNIX AND NOT APPLE)
+  install (TARGETS tuxmath tuxmathadmin
+    RUNTIME DESTINATION bin)
+endif(UNIX AND NOT APPLE)

Added: tuxmath/trunk/src/Makefile.am
===================================================================
--- tuxmath/trunk/src/Makefile.am	                        (rev 0)
+++ tuxmath/trunk/src/Makefile.am	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,99 @@
+## Makefile.am for tuxmath src:
+## Process with Automake to create Makefile.in
+
+# Support for gettext:
+datadir = @datadir@
+localedir = $(datadir)/locale
+DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@
+
+AM_CFLAGS=-Wall -g -DDATA_PREFIX=\"${DATA_PREFIX}\" -DDEBUG \
+	-DVERSION=\"@NAME_VERSION@\" -D$(SOUND)SOUND
+
+AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" \
+	-I../intl -I$(top_srcdir)/intl
+
+LDADD = @LIBINTL@ \
+  ../linebreak/liblinebreak.a
+
+
+
+
+if BUILD_MINGW32
+  bin_PROGRAMS = TuxMath
+  DATA_PREFIX=@MINGW32_PACKAGE_DATA_DIR@
+else
+  TUXMATHRC =
+  bin_PROGRAMS = tuxmath tuxmathadmin generate_lesson
+  DATA_PREFIX=${pkgdatadir}
+endif
+
+
+tuxmath_SOURCES = tuxmath.c \
+	setup.c 	\
+	titlescreen.c	\
+	game.c 		\
+	factoroids.c    \
+	fileops_media.c \
+	options.c	\
+	credits.c	\
+	highscore.c	\
+	linewrap.c	\
+	loaders.c	\
+	audio.c 	\
+	mathcards.c	\
+	campaign.c	\
+	multiplayer.c	\
+	fileops.c	\
+	convert_utf.c	\
+	SDL_extras.c	\
+	SDL_rotozoom.c	\
+	lessons.c	\
+	scandir.c	\
+	pixels.c	
+
+
+# HACK "TuxMath" is the Windows program, whereas "tuxmath" is the Unix program
+TuxMath_SOURCES  = $(tuxmath_SOURCES) tuxmathrc.rc
+
+tuxmathadmin_SOURCES = tuxmathadmin.c
+
+generate_lesson_SOURCES = generate_lesson.c	\
+		mathcards.c	\
+		options.c	\
+		fileops.c	\
+		lessons.c
+
+EXTRA_DIST = 	credits.h 	\
+	factoroids.h	\
+	fileops.h 	\
+	game.h		\
+	globals.h	\
+	highscore.h 	\
+	linewrap.h	\
+	loaders.h	\
+	mathcards.h 	\
+	options.h	\
+	setup.h		\
+	titlescreen.h	\
+	campaign.h	\
+	multiplayer.h	\
+	tuxmath.h	\
+	convert_utf.h	\
+	SDL_extras.h	\
+	SDL_rotozoom.h	\
+	lessons.h	\
+	gettext.h	\
+	scandir.h	\
+	pixels.h	\
+	compiler.h
+
+
+WINDRES=@WINDRES@
+
+# How to make an RC file
+tuxmathrc.o: tuxmathrc.rc
+	$(WINDRES) -i $< -o $@
+
+
+
+

Added: tuxmath/trunk/src/Makefile.in
===================================================================
--- tuxmath/trunk/src/Makefile.in	                        (rev 0)
+++ tuxmath/trunk/src/Makefile.in	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,636 @@
+# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008  Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+ at SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+ at BUILD_MINGW32_FALSE@bin_PROGRAMS = tuxmath$(EXEEXT) \
+ at BUILD_MINGW32_FALSE@	tuxmathadmin$(EXEEXT) \
+ at BUILD_MINGW32_FALSE@	generate_lesson$(EXEEXT)
+ at BUILD_MINGW32_TRUE@bin_PROGRAMS = TuxMath$(EXEEXT)
+subdir = src
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/codeset.m4 \
+	$(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/glibc2.m4 \
+	$(top_srcdir)/m4/glibc21.m4 $(top_srcdir)/m4/iconv.m4 \
+	$(top_srcdir)/m4/intdiv0.m4 $(top_srcdir)/m4/intl.m4 \
+	$(top_srcdir)/m4/intldir.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+	$(top_srcdir)/m4/intmax.m4 $(top_srcdir)/m4/inttypes-pri.m4 \
+	$(top_srcdir)/m4/inttypes_h.m4 $(top_srcdir)/m4/lcmessage.m4 \
+	$(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+	$(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/lock.m4 \
+	$(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/nls.m4 \
+	$(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/printf-posix.m4 \
+	$(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/size_max.m4 \
+	$(top_srcdir)/m4/stdint_h.m4 $(top_srcdir)/m4/uintmax_t.m4 \
+	$(top_srcdir)/m4/ulonglong.m4 $(top_srcdir)/m4/visibility.m4 \
+	$(top_srcdir)/m4/wchar_t.m4 $(top_srcdir)/m4/wint_t.m4 \
+	$(top_srcdir)/m4/xsize.m4 $(top_srcdir)/acinclude.m4 \
+	$(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+binPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
+PROGRAMS = $(bin_PROGRAMS)
+am__objects_1 = tuxmath.$(OBJEXT) setup.$(OBJEXT) \
+	titlescreen.$(OBJEXT) game.$(OBJEXT) factoroids.$(OBJEXT) \
+	fileops_media.$(OBJEXT) options.$(OBJEXT) credits.$(OBJEXT) \
+	highscore.$(OBJEXT) linewrap.$(OBJEXT) loaders.$(OBJEXT) \
+	audio.$(OBJEXT) mathcards.$(OBJEXT) campaign.$(OBJEXT) \
+	multiplayer.$(OBJEXT) fileops.$(OBJEXT) convert_utf.$(OBJEXT) \
+	SDL_extras.$(OBJEXT) SDL_rotozoom.$(OBJEXT) lessons.$(OBJEXT) \
+	scandir.$(OBJEXT) pixels.$(OBJEXT)
+am_TuxMath_OBJECTS = $(am__objects_1)
+TuxMath_OBJECTS = $(am_TuxMath_OBJECTS)
+TuxMath_LDADD = $(LDADD)
+TuxMath_DEPENDENCIES = ../linebreak/liblinebreak.a
+am_generate_lesson_OBJECTS = generate_lesson.$(OBJEXT) \
+	mathcards.$(OBJEXT) options.$(OBJEXT) fileops.$(OBJEXT) \
+	lessons.$(OBJEXT)
+generate_lesson_OBJECTS = $(am_generate_lesson_OBJECTS)
+generate_lesson_LDADD = $(LDADD)
+generate_lesson_DEPENDENCIES = ../linebreak/liblinebreak.a
+am_tuxmath_OBJECTS = tuxmath.$(OBJEXT) setup.$(OBJEXT) \
+	titlescreen.$(OBJEXT) game.$(OBJEXT) factoroids.$(OBJEXT) \
+	fileops_media.$(OBJEXT) options.$(OBJEXT) credits.$(OBJEXT) \
+	highscore.$(OBJEXT) linewrap.$(OBJEXT) loaders.$(OBJEXT) \
+	audio.$(OBJEXT) mathcards.$(OBJEXT) campaign.$(OBJEXT) \
+	multiplayer.$(OBJEXT) fileops.$(OBJEXT) convert_utf.$(OBJEXT) \
+	SDL_extras.$(OBJEXT) SDL_rotozoom.$(OBJEXT) lessons.$(OBJEXT) \
+	scandir.$(OBJEXT) pixels.$(OBJEXT)
+tuxmath_OBJECTS = $(am_tuxmath_OBJECTS)
+tuxmath_LDADD = $(LDADD)
+tuxmath_DEPENDENCIES = ../linebreak/liblinebreak.a
+am_tuxmathadmin_OBJECTS = tuxmathadmin.$(OBJEXT)
+tuxmathadmin_OBJECTS = $(am_tuxmathadmin_OBJECTS)
+tuxmathadmin_LDADD = $(LDADD)
+tuxmathadmin_DEPENDENCIES = ../linebreak/liblinebreak.a
+DEFAULT_INCLUDES = -I. at am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+SOURCES = $(TuxMath_SOURCES) $(generate_lesson_SOURCES) \
+	$(tuxmath_SOURCES) $(tuxmathadmin_SOURCES)
+DIST_SOURCES = $(TuxMath_SOURCES) $(generate_lesson_SOURCES) \
+	$(tuxmath_SOURCES) $(tuxmathadmin_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ALLOCA = @ALLOCA@
+AMTAR = @AMTAR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@
+CATOBJEXT = @CATOBJEXT@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CFLAG_VISIBILITY = @CFLAG_VISIBILITY@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATADIRNAME = @DATADIRNAME@
+DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GENCAT = @GENCAT@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIBC2 = @GLIBC2@
+GLIBC21 = @GLIBC21@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_ASPRINTF = @HAVE_ASPRINTF@
+HAVE_POSIX_PRINTF = @HAVE_POSIX_PRINTF@
+HAVE_SNPRINTF = @HAVE_SNPRINTF@
+HAVE_VISIBILITY = @HAVE_VISIBILITY@
+HAVE_WPRINTF = @HAVE_WPRINTF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INSTOBJEXT = @INSTOBJEXT@
+INTLBISON = @INTLBISON@
+INTLLIBS = @INTLLIBS@
+INTLOBJS = @INTLOBJS@
+INTL_LIBTOOL_SUFFIX_PREFIX = @INTL_LIBTOOL_SUFFIX_PREFIX@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMULTITHREAD = @LIBMULTITHREAD@
+LIBOBJS = @LIBOBJS@
+LIBPTH = @LIBPTH@
+LIBPTH_PREFIX = @LIBPTH_PREFIX@
+LIBS = @LIBS@
+LIBTHREAD = @LIBTHREAD@
+LTLIBC = @LTLIBC@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBMULTITHREAD = @LTLIBMULTITHREAD@
+LTLIBOBJS = @LTLIBOBJS@
+LTLIBPTH = @LTLIBPTH@
+LTLIBTHREAD = @LTLIBTHREAD@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NAME_VERSION = @NAME_VERSION@
+NSIS = @NSIS@
+NSI_DLL_DIR = @NSI_DLL_DIR@
+NSI_INSTALL_DIR = @NSI_INSTALL_DIR@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+POSUB = @POSUB@
+POW_LIB = @POW_LIB@
+PRI_MACROS_BROKEN = @PRI_MACROS_BROKEN@
+RANLIB = @RANLIB@
+SDL_CFLAGS = @SDL_CFLAGS@
+SDL_CONFIG = @SDL_CONFIG@
+SDL_LIBS = @SDL_LIBS@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WINDRES = @WINDRES@
+WOE32 = @WOE32@
+WOE32DLL = @WOE32DLL@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+
+# Support for gettext:
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = $(datadir)/locale
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CFLAGS = -Wall -g -DDATA_PREFIX=\"${DATA_PREFIX}\" -DDEBUG \
+	-DVERSION=\"@NAME_VERSION@\" -D$(SOUND)SOUND
+
+AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" \
+	-I../intl -I$(top_srcdir)/intl
+
+LDADD = @LIBINTL@ \
+  ../linebreak/liblinebreak.a
+
+ at BUILD_MINGW32_FALSE@DATA_PREFIX = ${pkgdatadir}
+ at BUILD_MINGW32_TRUE@DATA_PREFIX = @MINGW32_PACKAGE_DATA_DIR@
+ at BUILD_MINGW32_FALSE@TUXMATHRC = 
+tuxmath_SOURCES = tuxmath.c \
+	setup.c 	\
+	titlescreen.c	\
+	game.c 		\
+	factoroids.c    \
+	fileops_media.c \
+	options.c	\
+	credits.c	\
+	highscore.c	\
+	linewrap.c	\
+	loaders.c	\
+	audio.c 	\
+	mathcards.c	\
+	campaign.c	\
+	multiplayer.c	\
+	fileops.c	\
+	convert_utf.c	\
+	SDL_extras.c	\
+	SDL_rotozoom.c	\
+	lessons.c	\
+	scandir.c	\
+	pixels.c	
+
+
+# HACK "TuxMath" is the Windows program, whereas "tuxmath" is the Unix program
+TuxMath_SOURCES = $(tuxmath_SOURCES) tuxmathrc.rc
+tuxmathadmin_SOURCES = tuxmathadmin.c
+generate_lesson_SOURCES = generate_lesson.c	\
+		mathcards.c	\
+		options.c	\
+		fileops.c	\
+		lessons.c
+
+EXTRA_DIST = credits.h 	\
+	factoroids.h	\
+	fileops.h 	\
+	game.h		\
+	globals.h	\
+	highscore.h 	\
+	linewrap.h	\
+	loaders.h	\
+	mathcards.h 	\
+	options.h	\
+	setup.h		\
+	titlescreen.h	\
+	campaign.h	\
+	multiplayer.h	\
+	tuxmath.h	\
+	convert_utf.h	\
+	SDL_extras.h	\
+	SDL_rotozoom.h	\
+	lessons.h	\
+	gettext.h	\
+	scandir.h	\
+	pixels.h	\
+	compiler.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+		&& exit 0; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign  src/Makefile'; \
+	cd $(top_srcdir) && \
+	  $(AUTOMAKE) --foreign  src/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+install-binPROGRAMS: $(bin_PROGRAMS)
+	@$(NORMAL_INSTALL)
+	test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+	@list='$(bin_PROGRAMS)'; for p in $$list; do \
+	  p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+	  if test -f $$p \
+	  ; then \
+	    f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \
+	   echo " $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \
+	   $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \
+	  else :; fi; \
+	done
+
+uninstall-binPROGRAMS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(bin_PROGRAMS)'; for p in $$list; do \
+	  f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+	  echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \
+	  rm -f "$(DESTDIR)$(bindir)/$$f"; \
+	done
+
+clean-binPROGRAMS:
+	-test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+TuxMath$(EXEEXT): $(TuxMath_OBJECTS) $(TuxMath_DEPENDENCIES) 
+	@rm -f TuxMath$(EXEEXT)
+	$(LINK) $(TuxMath_OBJECTS) $(TuxMath_LDADD) $(LIBS)
+generate_lesson$(EXEEXT): $(generate_lesson_OBJECTS) $(generate_lesson_DEPENDENCIES) 
+	@rm -f generate_lesson$(EXEEXT)
+	$(LINK) $(generate_lesson_OBJECTS) $(generate_lesson_LDADD) $(LIBS)
+tuxmath$(EXEEXT): $(tuxmath_OBJECTS) $(tuxmath_DEPENDENCIES) 
+	@rm -f tuxmath$(EXEEXT)
+	$(LINK) $(tuxmath_OBJECTS) $(tuxmath_LDADD) $(LIBS)
+tuxmathadmin$(EXEEXT): $(tuxmathadmin_OBJECTS) $(tuxmathadmin_DEPENDENCIES) 
+	@rm -f tuxmathadmin$(EXEEXT)
+	$(LINK) $(tuxmathadmin_OBJECTS) $(tuxmathadmin_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/SDL_extras.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/SDL_rotozoom.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/audio.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/campaign.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/convert_utf.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/credits.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/factoroids.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/fileops.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/fileops_media.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/game.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/generate_lesson.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/highscore.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/lessons.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/linewrap.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/loaders.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/mathcards.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/multiplayer.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/options.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/pixels.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/scandir.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/setup.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/titlescreen.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/tuxmath.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/tuxmathadmin.Po at am__quote@
+
+.c.o:
+ at am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+ at am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	tags=; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	    $$tags $$unique; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	tags=; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$tags$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$tags $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && cd $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) $$here
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+	    fi; \
+	    cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+	  else \
+	    test -f $(distdir)/$$file \
+	    || cp -p $$d/$$file $(distdir)/$$file \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+	for dir in "$(DESTDIR)$(bindir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-info: install-info-am
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-ps: install-ps-am
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+	clean-generic ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-binPROGRAMS \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-info install-info-am install-man install-pdf \
+	install-pdf-am install-ps install-ps-am install-strip \
+	installcheck installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am uninstall-binPROGRAMS
+
+
+# How to make an RC file
+tuxmathrc.o: tuxmathrc.rc
+	$(WINDRES) -i $< -o $@
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:

Added: tuxmath/trunk/src/SDL_extras.c
===================================================================
--- tuxmath/trunk/src/SDL_extras.c	                        (rev 0)
+++ tuxmath/trunk/src/SDL_extras.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,811 @@
+/*
+*  C Implementation: SDL_extras
+*
+* Description: a few handy functions for using SDL graphics.
+*
+*
+* Author: David Bruce,,, <dbruce at tampabay.rr.com>, (C) 2007
+*
+* Copyright: GPL v3 or later
+*
+*/
+#include <math.h>
+
+#include "SDL_extras.h"
+#include "tuxmath.h"
+#include "pixels.h"
+
+#ifdef HAVE_LIBSDL_PANGO
+#include "SDL_Pango.h"
+SDLPango_Context* context = NULL;
+static SDLPango_Matrix* SDL_Colour_to_SDLPango_Matrix(const SDL_Color* cl);
+#endif
+
+
+/* DrawButton() creates and draws a translucent button with */
+/* rounded ends.  All colors and alpha values are supported.*/
+void DrawButton(SDL_Rect* target_rect,
+                int radius,
+                Uint8 r, Uint8 g, Uint8 b, Uint8 a)
+{
+  /* NOTE - we use a 32-bit temp surface even if we have a 16-bit */
+  /* screen - it gets converted during blitting.                  */
+  SDL_Surface* tmp_surf = SDL_CreateRGBSurface(SDL_SWSURFACE|SDL_SRCALPHA,
+                                          target_rect->w,
+                                          target_rect->h,
+                                          32,
+                                          rmask, gmask, bmask, amask);
+  Uint32 color = SDL_MapRGBA(tmp_surf->format, r, g, b, a);
+  SDL_FillRect(tmp_surf, NULL, color);
+  RoundCorners(tmp_surf, radius);
+
+  SDL_BlitSurface(tmp_surf, NULL, screen, target_rect);
+  SDL_FreeSurface(tmp_surf);
+}
+
+
+
+void RoundCorners(SDL_Surface* s, Uint16 radius)
+{
+  int y = 0;
+  int x_dist, y_dist;
+  Uint32* p = NULL;
+  Uint32 alpha_mask;
+  int bytes_per_pix;
+
+  if (!s)
+    return;
+  if (SDL_LockSurface(s) == -1)
+    return;
+
+  bytes_per_pix = s->format->BytesPerPixel;
+  if (bytes_per_pix != 4)
+    return;
+
+  /* radius cannot be more than half of width or height: */
+  if (radius > (s->w)/2)
+    radius = (s->w)/2;
+  if (radius > (s->h)/2)
+    radius = (s->h)/2;
+
+
+  alpha_mask = s->format->Amask;
+
+  /* Now round off corners: */
+  /* upper left:            */
+  for (y = 0; y < radius; y++)
+  {
+    p = (Uint32*)(s->pixels + (y * s->pitch));
+    x_dist = radius;
+    y_dist = radius - y;
+
+    while (((x_dist * x_dist) + (y_dist * y_dist)) > (radius * radius))
+    {
+      /* (make pixel (x,y) transparent) */
+      *p = *p & ~alpha_mask;
+      p++;
+      x_dist--;
+    }
+  }
+
+  /* upper right:            */
+  for (y = 0; y < radius; y++)
+  {
+    /* start at end of top row: */
+    p = (Uint32*)(s->pixels + ((y + 1) * s->pitch) - bytes_per_pix);
+
+    x_dist = radius;
+    y_dist = radius - y;
+
+    while (((x_dist * x_dist) + (y_dist * y_dist)) > (radius * radius))
+    {
+      /* (make pixel (x,y) transparent) */
+      *p = *p & ~alpha_mask;
+      p--;
+      x_dist--;
+    }
+  }
+
+  /* bottom left:            */
+  for (y = (s->h - 1); y > (s->h - radius); y--)
+  {
+    /* start at beginning of bottom row */
+    p = (Uint32*)(s->pixels + (y * s->pitch));
+    x_dist = radius;
+    y_dist = y - (s->h - radius);
+
+    while (((x_dist * x_dist) + (y_dist * y_dist)) > (radius * radius))
+    {
+      /* (make pixel (x,y) transparent) */
+      *p = *p & ~alpha_mask;
+      p++;
+      x_dist--;
+    }
+  }
+
+  /* bottom right:            */
+  for (y = (s->h - 1); y > (s->h - radius); y--)
+  {
+    /* start at end of bottom row */
+    p = (Uint32*)(s->pixels + ((y + 1) * s->pitch) - bytes_per_pix);
+    x_dist = radius;
+    y_dist = y - (s->h - radius);
+
+    while (((x_dist * x_dist) + (y_dist * y_dist)) > (radius * radius))
+    {
+      /* (make pixel (x,y) transparent) */
+      *p = *p & ~alpha_mask;
+      p--;
+      x_dist--;
+    }
+  }
+  SDL_UnlockSurface(s);
+}
+
+
+/**********************
+ Flip:
+   input: a SDL_Surface, x, y
+   output: a copy of the SDL_Surface flipped via rules:
+
+     if x is a nonzero value, then flip horizontally
+     if y is a nonzero value, then flip vertically
+
+     note: you can have it flip both
+**********************/
+SDL_Surface* Flip( SDL_Surface *in, int x, int y ) {
+        SDL_Surface *out, *tmp;
+        SDL_Rect from_rect, to_rect;
+        Uint32        flags;
+        Uint32  colorkey=0;
+
+        /* --- grab the settings for the incoming pixmap --- */
+
+        SDL_LockSurface(in);
+        flags = in->flags;
+
+        /* --- change in's flags so ignore colorkey & alpha --- */
+
+        if (flags & SDL_SRCCOLORKEY) {
+                in->flags &= ~SDL_SRCCOLORKEY;
+                colorkey = in->format->colorkey;
+        }
+        if (flags & SDL_SRCALPHA) {
+                in->flags &= ~SDL_SRCALPHA;
+        }
+
+        SDL_UnlockSurface(in);
+
+        /* --- create our new surface --- */
+
+        out = SDL_CreateRGBSurface(
+                SDL_SWSURFACE,
+                in->w, in->h, 32, rmask, gmask, bmask, amask);
+
+        /* --- flip horizontally if requested --- */
+
+        if (x) {
+                from_rect.h = to_rect.h = in->h;
+                from_rect.w = to_rect.w = 1;
+                from_rect.y = to_rect.y = 0;
+                from_rect.x = 0;
+                to_rect.x = in->w - 1;
+
+                do {
+                        SDL_BlitSurface(in, &from_rect, out, &to_rect);
+                        from_rect.x++;
+                        to_rect.x--;
+                } while (to_rect.x >= 0);
+        }
+
+        /* --- flip vertically if requested --- */
+
+        if (y) {
+                from_rect.h = to_rect.h = 1;
+                from_rect.w = to_rect.w = in->w;
+                from_rect.x = to_rect.x = 0;
+                from_rect.y = 0;
+                to_rect.y = in->h - 1;
+
+                do {
+                        SDL_BlitSurface(in, &from_rect, out, &to_rect);
+                        from_rect.y++;
+                        to_rect.y--;
+                } while (to_rect.y >= 0);
+        }
+
+        /* --- restore colorkey & alpha on in and setup out the same --- */
+
+        SDL_LockSurface(in);
+
+        if (flags & SDL_SRCCOLORKEY) {
+                in->flags |= SDL_SRCCOLORKEY;
+                in->format->colorkey = colorkey;
+                tmp = SDL_DisplayFormat(out);
+                SDL_FreeSurface(out);
+                out = tmp;
+                out->flags |= SDL_SRCCOLORKEY;
+                out->format->colorkey = colorkey;
+        } else if (flags & SDL_SRCALPHA) {
+                in->flags |= SDL_SRCALPHA;
+                tmp = SDL_DisplayFormatAlpha(out);
+                SDL_FreeSurface(out);
+                out = tmp;
+        } else {
+                tmp = SDL_DisplayFormat(out);
+                SDL_FreeSurface(out);
+                out = tmp;
+        }
+
+        SDL_UnlockSurface(in);
+
+        return out;
+}
+
+/* Blend two surfaces together. The third argument is between 0.0 and
+   1.0, and represents the weight assigned to the first surface.  If
+   the pointer to the second surface is NULL, this performs fading.
+
+   Currently this works only with RGBA images, but this is largely to
+   make the (fast) pointer arithmetic work out; it could be easily
+   generalized to other image types. */
+SDL_Surface* Blend(SDL_Surface *S1,SDL_Surface *S2,float gamma)
+{
+  SDL_PixelFormat *fmt1, *fmt2;
+  Uint8 r1, r2, g1, g2, b1, b2, a1, a2;
+  SDL_Surface *tmpS, *ret;
+  Uint32 *cpix1, *epix1, *cpix2, *epix2;
+  float gamflip;
+
+  if (!S1)
+    return NULL;
+
+  fmt1 = fmt2 = NULL;
+  tmpS = ret = NULL;
+
+  gamflip = 1.0 - gamma;
+  if (gamma < 0 || gamflip < 0)
+  {
+    perror("gamma must be between 0 and 1");
+    exit(0);
+  }
+
+  fmt1 = S1->format;
+
+  if (fmt1 && fmt1->BitsPerPixel != 32)
+  {
+    perror("This works only with RGBA images");
+    return S1;
+  }
+  if (S2 != NULL)
+  {
+    fmt2 = S2->format;
+    if (fmt2->BitsPerPixel != 32)
+    {
+      perror("This works only with RGBA images");
+      return S1;
+    }
+    // Check that both images have the same width dimension
+    if (S1->w != S2->w)
+    {
+      printf("S1->w %d, S2->w %d;  S1->h %d, S2->h %d\n",
+             S1->w, S2->w, S1->h, S2->h);
+      printf("Both images must have the same width dimensions\n");
+      return S1;
+    }
+  }
+
+  tmpS = SDL_ConvertSurface(S1, fmt1, SDL_SWSURFACE);
+  if (tmpS == NULL)
+  {
+    perror("SDL_ConvertSurface() failed");
+    return S1; 
+  }
+  if (-1 == SDL_LockSurface(tmpS))
+  {
+    perror("SDL_LockSurface() failed");
+    return S1; 
+  }
+
+  // We're going to go through the pixels in reverse order, to start
+  // from the bottom of each image. That way, we can blend things that
+  // are not of the same height and have them align at the bottom.
+  // So the "ending pixel" (epix) will be before the first pixel, and
+  // the current pixel (cpix) will be the last pixel.
+  epix1 = (Uint32*) tmpS->pixels - 1;
+  cpix1 = epix1 + tmpS->w * tmpS->h;
+  if (S2 != NULL
+      && (SDL_LockSurface(S2) != -1))
+  {
+    epix2 = (Uint32*) S2->pixels - 1;
+    cpix2 = epix2 + S2->w * S2->h;
+  }
+  else
+  {
+    epix2 = epix1;
+    cpix2 = cpix1;
+  }
+
+  for (; cpix1 > epix1; cpix1--, cpix2--)
+  {
+    SDL_GetRGBA(*cpix1, fmt1, &r1, &g1, &b1, &a1);
+    a1 = gamma * a1;
+    if (S2 != NULL && cpix2 > epix2)
+    {
+      SDL_GetRGBA(*cpix2, fmt2, &r2, &g2, &b2, &a2);
+      r1 = gamma * r1 + gamflip * r2;
+      g1 = gamma * g1 + gamflip * g2;
+      b1 = gamma * b1 + gamflip * b2;
+      a1 += gamflip * a2;
+    }
+    *cpix1 = SDL_MapRGBA(fmt1,r1,g1,b1,a1);
+  }
+
+  SDL_UnlockSurface(tmpS);
+
+  if (S2 != NULL)
+    SDL_UnlockSurface(S2);
+
+  ret = SDL_DisplayFormatAlpha(tmpS);
+  SDL_FreeSurface(tmpS);
+
+  return ret;
+}
+
+
+#ifdef HAVE_LIBSDL_PANGO
+
+void init_SDLPango_Context()
+{
+   context =  SDLPango_CreateContext_GivenFontDesc(DEFAULT_FONT_NAME);
+}
+
+void free_SDLPango_Context()
+{
+  if(context != NULL)
+    SDLPango_FreeContext(context);
+  context = NULL;
+}
+
+
+SDLPango_Matrix* SDL_Colour_to_SDLPango_Matrix(const SDL_Color *cl)
+{
+  SDLPango_Matrix *colour;
+  colour=malloc(sizeof(SDLPango_Matrix));
+  int k;
+  for(k=0;k<4;k++){
+  	(*colour).m[0][k]=(*cl).r;
+  	(*colour).m[1][k]=(*cl).g;
+  	(*colour).m[2][k]=(*cl).b;
+  }
+  (*colour).m[3][0]=0;
+  (*colour).m[3][1]=255;
+  (*colour).m[3][2]=0;
+  (*colour).m[3][3]=0;
+
+  return colour;
+}
+
+#endif
+
+
+/* BlackOutline() creates a surface containing text of the designated */
+/* foreground color, surrounded by a black shadow, on a transparent    */
+/* background.  The appearance can be tuned by adjusting the number of */
+/* background copies and the offset where the foreground text is       */
+/* finally written (see below).                                        */
+SDL_Surface* BlackOutline(const char *t, TTF_Font *font, SDL_Color *c)
+{
+  SDL_Surface* out = NULL;
+  SDL_Surface* black_letters = NULL;
+  SDL_Surface* white_letters = NULL;
+  SDL_Surface* bg = NULL;
+  SDL_Rect dstrect;
+  Uint32 color_key;
+
+  if (!t || !font || !c)
+  {
+    fprintf(stderr, "BlackOutline(): invalid ptr parameter, returning.");
+    return NULL;
+  }
+
+  if (t[0] == '\0')
+  {
+    fprintf(stderr, "BlackOutline(): empty string, returning");
+    return NULL;
+  }
+
+#ifdef TUXMATH_DEBUG
+  fprintf( stderr, "\nEntering BlackOutline(): \n");
+  fprintf( stderr, "BlackOutline of \"%s\"\n", t );
+#endif
+
+#ifndef HAVE_LIBSDL_PANGO
+  black_letters = TTF_RenderUTF8_Blended(font, t, black);
+#else
+  if( context != NULL)
+  {
+    SDLPango_SetDefaultColor(context, MATRIX_TRANSPARENT_BACK_BLACK_LETTER);
+    SDLPango_SetText(context, t, -1);
+    black_letters = SDLPango_CreateSurfaceDraw(context);
+  }
+  else {
+    black_letters = TTF_RenderUTF8_Blended(font, t, black);
+  }
+#endif
+
+  if (!black_letters)
+  {
+    fprintf (stderr, "Warning - BlackOutline() could not create image for %s\n", t);
+    return NULL;
+  }
+
+  bg = SDL_CreateRGBSurface(SDL_SWSURFACE,
+                            (black_letters->w) + 5,
+                            (black_letters->h) + 5,
+                             32,
+                             rmask, gmask, bmask, amask);
+  /* Use color key for eventual transparency: */
+  color_key = SDL_MapRGB(bg->format, 01, 01, 01);
+  SDL_FillRect(bg, NULL, color_key);
+
+  /* Now draw black outline/shadow 2 pixels on each side: */
+  dstrect.w = black_letters->w;
+  dstrect.h = black_letters->h;
+
+  /* NOTE: can make the "shadow" more or less pronounced by */
+  /* changing the parameters of these loops.                */
+  for (dstrect.x = 1; dstrect.x < 4; dstrect.x++)
+    for (dstrect.y = 1; dstrect.y < 3; dstrect.y++)
+      SDL_BlitSurface(black_letters , NULL, bg, &dstrect );
+
+  SDL_FreeSurface(black_letters);
+
+  /* --- Put the color version of the text on top! --- */
+#ifndef HAVE_LIBSDL_PANGO
+  white_letters = TTF_RenderUTF8_Blended(font, t, *c);
+#else
+  if( context != NULL)
+  {
+    /* convert color arg: */
+    SDLPango_Matrix* color_matrix = SDL_Colour_to_SDLPango_Matrix(c);
+
+    if (color_matrix)
+      SDLPango_SetDefaultColor(context, color_matrix);
+    else  /* fall back to just using white if conversion fails: */
+      SDLPango_SetDefaultColor(context, MATRIX_TRANSPARENT_BACK_WHITE_LETTER);
+
+    white_letters = SDLPango_CreateSurfaceDraw(context);
+  }
+  else
+  {
+    white_letters = TTF_RenderUTF8_Blended(font, t, *c);
+  }
+#endif
+
+  dstrect.x = 1;
+  dstrect.y = 1;
+  SDL_BlitSurface(white_letters, NULL, bg, &dstrect);
+  SDL_FreeSurface(white_letters);
+
+  /* --- Convert to the screen format for quicker blits --- */
+  SDL_SetColorKey(bg, SDL_SRCCOLORKEY|SDL_RLEACCEL, color_key);
+  out = SDL_DisplayFormatAlpha(bg);
+  SDL_FreeSurface(bg);
+
+#ifdef TUXMATH_DEBUG
+  fprintf( stderr, "\nLeaving BlackOutline(): \n");
+#endif
+
+  return out;
+}
+
+
+/* This (fast) function just returns a non-outlined surf */
+/* using SDL_Pango if available, SDL_ttf as fallback     */
+SDL_Surface* SimpleText(const char *t, TTF_Font* font, SDL_Color* col)
+{
+  SDL_Surface* surf = NULL;
+  int using_pango = 0;
+
+#ifdef HAVE_LIBSDL_PANGO
+  using_pango = (context != NULL);
+#endif
+
+  if (using_pango) {
+#ifdef HAVE_LIBSDL_PANGO
+  SDLPango_Matrix colormatrix = {
+    col->r,  col->r,  0,  0,
+    col->g,  col->g,  0,  0,
+    col->b,  col->b,  0,  0,
+    0,      255,      0,  0,
+  };
+  SDLPango_SetDefaultColor(context, &colormatrix );
+  SDLPango_SetText(context, t, -1);
+  surf = SDLPango_CreateSurfaceDraw(context);
+#endif
+  }
+  else {
+    surf = TTF_RenderUTF8_Blended(font, t, *col);
+  }
+
+  return surf;
+}
+
+
+/* This (fast) function just returns a non-outlined surf */
+/* using SDL_Pango if available, SDL_ttf as fallback     */
+SDL_Surface* SimpleTextWithOffset(const char *t, TTF_Font* font, SDL_Color* col, int *glyph_offset)
+{
+  SDL_Surface* surf = NULL;
+  int using_pango = 0;
+
+#ifdef HAVE_LIBSDL_PANGO
+  using_pango = (context != NULL);
+#endif
+
+  if (using_pango) {
+#ifdef HAVE_LIBSDL_PANGO
+  SDLPango_Matrix colormatrix = {
+    col->r,  col->r,  0,  0,
+    col->g,  col->g,  0,  0,
+    col->b,  col->b,  0,  0,
+    0,      255,      0,  0,
+  };
+  SDLPango_SetDefaultColor(context, &colormatrix );
+  SDLPango_SetText(context, t, -1);
+  surf = SDLPango_CreateSurfaceDraw(context);
+  *glyph_offset = 0; // fixme?
+#endif
+  }
+  else {
+    surf = TTF_RenderUTF8_Blended(font, t, *col);
+    int h;
+    int hmax = 0;
+    int len = strlen(t);
+    int i;
+    for (i = 0; i < len; i++) {
+      TTF_GlyphMetrics(font, t[i], NULL, NULL, NULL, &h, NULL);
+      if (h > hmax)
+	hmax = h;
+    }
+    *glyph_offset = hmax - TTF_FontAscent(font);
+  }
+
+  return surf;
+}
+
+
+
+
+int inRect( SDL_Rect r, int x, int y) {
+        if ((x < r.x) || (y < r.y) || (x > r.x + r.w) || (y > r.y + r.h))
+                return 0;
+        return 1;
+}
+
+/* Darkens the screen by a factor of 2^bits */
+void DarkenScreen(Uint8 bits)
+{
+#if PIXEL_BITS == 32
+  Uint32* p;
+#elif PIXEL_BITS == 16
+  Uint16* p;
+#else
+  return;
+#endif
+  Uint32 rm = screen->format->Rmask;
+  Uint32 gm = screen->format->Gmask;
+  Uint32 bm = screen->format->Bmask;
+
+
+  int x, y;
+
+  /* (realistically, 1 and 2 are the only useful values) */
+  if (bits > 8)
+    return;
+
+  p = screen->pixels;
+
+  for (y = 0; y < screen->h; y++)
+  {
+    for (x = 0; x < screen->w; x++)
+    {
+      *p = (((*p&rm)>>bits)&rm)
+         | (((*p&gm)>>bits)&gm)
+         | (((*p&bm)>>bits)&bm);
+      p++;
+    }
+  }
+}
+
+
+void SwitchScreenMode(void)
+{
+  int window = (screen->flags & SDL_FULLSCREEN);
+  SDL_Surface* oldscreen = screen;
+
+  if (!window)
+  {
+    screen = SDL_SetVideoMode(fs_res_x,
+                              fs_res_y,
+                              PIXEL_BITS,
+                              SDL_SWSURFACE|SDL_HWPALETTE|SDL_FULLSCREEN);
+  }
+  else
+  {
+    screen = SDL_SetVideoMode(RES_X,
+                              RES_Y,
+                              PIXEL_BITS,
+                              SDL_SWSURFACE|SDL_HWPALETTE);
+
+  }
+
+  if (screen == NULL)
+  {
+    fprintf(stderr,
+            "\nError: I could not switch to %s mode.\n"
+            "The Simple DirectMedia error that occured was:\n"
+            "%s\n\n",
+            window ? "windowed" : "fullscreen",
+            SDL_GetError());
+    screen = oldscreen;
+  }
+  else
+  {
+    SDL_FreeSurface(oldscreen);
+    oldscreen = NULL;
+    SDL_UpdateRect(screen, 0, 0, 0, 0);
+  }
+
+}
+
+/*
+Block application until SDL receives an appropriate event. Events can be
+a single or OR'd combination of event masks. 
+e.g. e = WaitForEvent(SDL_KEYDOWNMASK | SDL_QUITMASK)
+*/
+SDL_EventType WaitForEvent(SDL_EventMask events)
+{
+  SDL_Event evt;
+  while (1)
+  {
+    while (SDL_PollEvent(&evt) )
+    {
+      if (SDL_EVENTMASK(evt.type) & events)
+        return evt.type;
+      else 
+        SDL_Delay(50);
+    }
+  }
+}
+/* Swiped shamelessly from TuxPaint
+   Based on code from: http://www.codeproject.com/cs/media/imageprocessing4.asp
+   copyright 2002 Christian Graus */
+
+SDL_Surface* zoom(SDL_Surface* src, int new_w, int new_h)
+{
+  SDL_Surface* s;
+
+  /* These function pointers will point to the appropriate */
+  /* putpixel() and getpixel() variants to be used in the  */
+  /* current colorspace:                                   */
+  void (*putpixel) (SDL_Surface*, int, int, Uint32);
+  Uint32(*getpixel) (SDL_Surface*, int, int);
+
+  float xscale, yscale;
+  int x, y;
+  int floor_x, ceil_x,
+        floor_y, ceil_y;
+  float fraction_x, fraction_y,
+        one_minus_x, one_minus_y;
+  float n1, n2;
+  Uint8 r1, g1, b1, a1;
+  Uint8 r2, g2, b2, a2;
+  Uint8 r3, g3, b3, a3;
+  Uint8 r4, g4, b4, a4;
+  Uint8 r, g, b, a;
+
+  tmdprintf("\nEntering zoom():\n");
+
+  /* Create surface for zoom: */
+
+  s = SDL_CreateRGBSurface(src->flags,        /* SDL_SWSURFACE, */
+                           new_w, new_h, src->format->BitsPerPixel,
+                           src->format->Rmask,
+                           src->format->Gmask,
+                           src->format->Bmask,
+                           src->format->Amask);
+
+  if (s == NULL)
+  {
+    fprintf(stderr, "\nError: Can't build zoom surface\n"
+            "The Simple DirectMedia Layer error that occurred was:\n"
+            "%s\n\n", SDL_GetError());
+    return NULL;
+//    cleanup();
+//    exit(1);
+  }
+
+  tmdprintf("orig surface %dx%d, %d bytes per pixel\n",
+            src->w, src->h, src->format->BytesPerPixel);
+  tmdprintf("new surface %dx%d, %d bytes per pixel\n",
+            s->w, s->h, s->format->BytesPerPixel);
+
+  /* Now assign function pointers to correct functions based */
+  /* on data format of original and zoomed surfaces:         */
+  getpixel = getpixels[src->format->BytesPerPixel];
+  putpixel = putpixels[s->format->BytesPerPixel];
+
+  SDL_LockSurface(src);
+  SDL_LockSurface(s);
+
+  xscale = (float) src->w / (float) new_w;
+  yscale = (float) src->h / (float) new_h;
+
+  for (x = 0; x < new_w; x++)
+  {
+    for (y = 0; y < new_h; y++)
+    {
+      /* Here we calculate the new RGBA values for each pixel */
+      /* using a "weighted average" of the four pixels in the */
+      /* corresponding location in the orginal surface:       */
+
+      /* figure out which original pixels to use in the calc: */
+      floor_x = floor((float) x * xscale);
+      ceil_x = floor_x + 1;
+      if (ceil_x >= src->w)
+        ceil_x = floor_x;
+
+      floor_y = floor((float) y * yscale);
+      ceil_y = floor_y + 1;
+      if (ceil_y >= src->h)
+        ceil_y = floor_y;
+
+      fraction_x = x * xscale - floor_x;
+      fraction_y = y * yscale - floor_y;
+
+      one_minus_x = 1.0 - fraction_x;
+      one_minus_y = 1.0 - fraction_y;
+
+      /* Grab their values:  */
+      SDL_GetRGBA(getpixel(src, floor_x, floor_y), src->format,
+                  &r1, &g1, &b1, &a1);
+      SDL_GetRGBA(getpixel(src, ceil_x,  floor_y), src->format,
+                  &r2, &g2, &b2, &a2);
+      SDL_GetRGBA(getpixel(src, floor_x, ceil_y),  src->format,
+                  &r3, &g3, &b3, &a3);
+      SDL_GetRGBA(getpixel(src, ceil_x,  ceil_y),  src->format,
+                  &r4, &g4, &b4, &a4);
+
+      /* Create the weighted averages: */
+      n1 = (one_minus_x * r1 + fraction_x * r2);
+      n2 = (one_minus_x * r3 + fraction_x * r4);
+      r = (one_minus_y * n1 + fraction_y * n2);
+
+      n1 = (one_minus_x * g1 + fraction_x * g2);
+      n2 = (one_minus_x * g3 + fraction_x * g4);
+      g = (one_minus_y * n1 + fraction_y * n2);
+
+      n1 = (one_minus_x * b1 + fraction_x * b2);
+      n2 = (one_minus_x * b3 + fraction_x * b4);
+      b = (one_minus_y * n1 + fraction_y * n2);
+
+      n1 = (one_minus_x * a1 + fraction_x * a2);
+      n2 = (one_minus_x * a3 + fraction_x * a4);
+      a = (one_minus_y * n1 + fraction_y * n2);
+
+      /* and put them into our new surface: */
+      putpixel(s, x, y, SDL_MapRGBA(s->format, r, g, b, a));
+
+    }
+  }
+
+  SDL_UnlockSurface(s);
+  SDL_UnlockSurface(src);
+
+  tmdprintf("\nLeaving zoom():\n");
+
+  return s;
+}
+

Added: tuxmath/trunk/src/SDL_extras.h
===================================================================
--- tuxmath/trunk/src/SDL_extras.h	                        (rev 0)
+++ tuxmath/trunk/src/SDL_extras.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,51 @@
+//
+// C Interface: SDL_extras
+//
+// Description: A few handy functions for using SDL graphics.
+//
+//
+// Author: David Bruce,,, <dbruce at tampabay.rr.com>, (C) 2007
+//
+// Copyright: See COPYING file that comes with this distribution
+// (briefly, GPL v3 or later).
+//
+
+#ifndef SDL_EXTRAS_H
+#define SDL_EXTRAS_H
+
+#include "SDL.h"
+#include "SDL_ttf.h"
+
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+#define rmask 0xff000000
+#define gmask 0x00ff0000
+#define bmask 0x0000ff00
+#define amask 0x000000ff
+#else
+#define rmask 0x000000ff
+#define gmask 0x0000ff00
+#define bmask 0x00ff0000
+#define amask 0xff000000
+#endif
+
+
+#ifdef HAVE_LIBSDL_PANGO
+void init_SDLPango_Context();
+void free_SDLPango_Context();
+#endif
+
+void DrawButton(SDL_Rect* target_rect, int radius, Uint8 r, Uint8 g, Uint8 b, Uint8 a);
+void RoundCorners(SDL_Surface* s, Uint16 radius);
+SDL_Surface* Flip(SDL_Surface *in, int x, int y);
+SDL_Surface* BlackOutline(const char *t, TTF_Font* font, SDL_Color* c);
+SDL_Surface* SimpleText(const char *t, TTF_Font* font, SDL_Color* col);
+SDL_Surface* SimpleTextWithOffset(const char *t, TTF_Font* font, SDL_Color* col, int *glyph_offset);
+int  inRect(SDL_Rect r, int x, int y);
+void DarkenScreen(Uint8 bits);
+void SwitchScreenMode(void);
+SDL_EventType WaitForEvent(SDL_EventMask events);
+SDL_Surface* Blend(SDL_Surface *S1, SDL_Surface *S2,float gamma);
+SDL_Surface* zoom(SDL_Surface* src, int new_w, int new_h);
+
+
+#endif

Added: tuxmath/trunk/src/SDL_rotozoom.c
===================================================================
--- tuxmath/trunk/src/SDL_rotozoom.c	                        (rev 0)
+++ tuxmath/trunk/src/SDL_rotozoom.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,1355 @@
+/*  
+
+  The SDL_rotozoom sources were copied from the SDL_gfx library and
+  are relicensed, only for the purposes of TuxMath, to GPL.  Thanks to
+  Andreas Schiffler.
+
+  SDL_rotozoom.c - rotozoomer for 32bit or 8bit surfaces
+
+  LGPL (c) A. Schiffler
+
+*/
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "SDL_rotozoom.h"
+
+#define MAX(a,b)    (((a) > (b)) ? (a) : (b))
+
+
+/* 
+ 
+ 32bit integer-factor averaging Shrinker
+
+ Shrinks 32bit RGBA/ABGR 'src' surface to 'dst' surface.
+ 
+*/
+
+int shrinkSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int factorx, int factory)
+{
+    int x, y, dx, dy, sgap, dgap, ra, ga, ba, aa;
+    int n_average;
+    tColorRGBA *sp, *osp, *oosp;
+    tColorRGBA *dp;
+
+    /*
+     * Averaging integer shrink
+     */
+
+    /* Precalculate division factor */
+    n_average = factorx*factory;
+   
+    /*
+     * Scan destination
+     */
+    sp = (tColorRGBA *) src->pixels;
+    sgap = src->pitch - src->w * 4;
+
+    dp = (tColorRGBA *) dst->pixels;
+    dgap = dst->pitch - dst->w * 4;
+
+    for (y = 0; y < dst->h; y++) {
+
+      osp=sp;
+      for (x = 0; x < dst->w; x++) {
+
+        /* Trace out source box and accumulate */
+        oosp=sp;
+        ra=ga=ba=aa=0;
+        for (dy=0; dy < factory; dy++) {
+         for (dx=0; dx < factorx; dx++) {
+          ra += sp->r;
+          ga += sp->g;
+          ba += sp->b;
+          aa += sp->a;
+          
+          sp++;
+         } // src dx loop
+         sp = (tColorRGBA *)((Uint8*)sp + (src->pitch - 4*factorx)); // next y
+        } // src dy loop
+
+        // next box-x
+        sp = (tColorRGBA *)((Uint8*)oosp + 4*factorx);
+                
+        /* Store result in destination */
+        dp->r = ra/n_average;
+        dp->g = ga/n_average;
+        dp->b = ba/n_average;
+        dp->a = aa/n_average;
+                 
+        /*
+         * Advance destination pointer 
+         */
+         dp++;
+        } // dst x loop
+
+        // next box-y
+        sp = (tColorRGBA *)((Uint8*)osp + src->pitch*factory);
+
+        /*
+         * Advance destination pointers 
+         */
+        dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
+      } // dst y loop
+
+    return (0);
+}
+
+/* 
+ 
+ 8bit integer-factor averaging Shrinker
+
+ Shrinks 8bit Y 'src' surface to 'dst' surface.
+ 
+*/
+
+int shrinkSurfaceY(SDL_Surface * src, SDL_Surface * dst, int factorx, int factory)
+{
+    int x, y, dx, dy, sgap, dgap, a;
+    int n_average;
+    Uint8 *sp, *osp, *oosp;
+    Uint8 *dp;
+
+    /*
+     * Averaging integer shrink
+     */
+
+    /* Precalculate division factor */
+    n_average = factorx*factory;
+   
+    /*
+     * Scan destination
+     */
+    sp = (Uint8 *) src->pixels;
+    sgap = src->pitch - src->w;
+
+    dp = (Uint8 *) dst->pixels;
+    dgap = dst->pitch - dst->w;
+    
+    for (y = 0; y < dst->h; y++) {    
+
+      osp=sp;
+      for (x = 0; x < dst->w; x++) {
+
+        /* Trace out source box and accumulate */
+        oosp=sp;
+        a=0;
+        for (dy=0; dy < factory; dy++) {
+         for (dx=0; dx < factorx; dx++) {
+          a += (*sp);           
+          sp++; // next x
+         } // src dx loop         
+         sp = (Uint8 *)((Uint8*)sp + (src->pitch - factorx)); // next y
+        } // src dy loop
+        
+        // next box-x
+        sp = (Uint8 *)((Uint8*)oosp + factorx);
+                
+        /* Store result in destination */
+        *dp = a/n_average;
+
+        /*
+         * Advance destination pointer 
+         */
+         dp++;
+        } // dst x loop
+
+        // next box-y
+        sp = (Uint8 *)((Uint8*)osp + src->pitch*factory);
+
+        /*
+         * Advance destination pointers 
+         */
+        dp = (Uint8 *)((Uint8 *)dp + dgap);
+      } // dst y loop
+
+    return (0);
+}
+
+/* 
+ 
+ 32bit Zoomer with optional anti-aliasing by bilinear interpolation.
+
+ Zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface.
+ 
+*/
+
+int zoomSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int flipx, int flipy, int smooth)
+{
+    int x, y, sx, sy, *sax, *say, *csax, *csay, csx, csy, ex, ey, t1, t2, sstep;
+    tColorRGBA *c00, *c01, *c10, *c11;
+    tColorRGBA *sp, *csp, *dp;
+    int dgap;
+
+    /*
+     * Variable setup 
+     */
+    if (smooth) {
+	/*
+	 * For interpolation: assume source dimension is one pixel 
+	 */
+	/*
+	 * smaller to avoid overflow on right and bottom edge.     
+	 */
+	sx = (int) (65536.0 * (float) (src->w - 1) / (float) dst->w);
+	sy = (int) (65536.0 * (float) (src->h - 1) / (float) dst->h);
+    } else {
+	sx = (int) (65536.0 * (float) src->w / (float) dst->w);
+	sy = (int) (65536.0 * (float) src->h / (float) dst->h);
+    }
+
+    /*
+     * Allocate memory for row increments 
+     */
+    if ((sax = (int *) malloc((dst->w + 1) * sizeof(Uint32))) == NULL) {
+	return (-1);
+    }
+    if ((say = (int *) malloc((dst->h + 1) * sizeof(Uint32))) == NULL) {
+	free(sax);
+	return (-1);
+    }
+
+    /*
+     * Precalculate row increments 
+     */
+    sp = csp = (tColorRGBA *) src->pixels;
+    dp = (tColorRGBA *) dst->pixels;
+
+    if (flipx) csp += (src->w-1);
+    if (flipy) csp  = (tColorRGBA*)( (Uint8*)csp + src->pitch*(src->h-1) );
+
+    csx = 0;
+    csax = sax;
+    for (x = 0; x <= dst->w; x++) {
+	*csax = csx;
+	csax++;
+	csx &= 0xffff;
+	csx += sx;
+    }
+    csy = 0;
+    csay = say;
+    for (y = 0; y <= dst->h; y++) {
+	*csay = csy;
+	csay++;
+	csy &= 0xffff;
+	csy += sy;
+    }
+
+    dgap = dst->pitch - dst->w * 4;
+
+    /*
+     * Switch between interpolating and non-interpolating code 
+     */
+    if (smooth) {
+
+	/*
+	 * Interpolating Zoom 
+	 */
+
+	/*
+	 * Scan destination 
+	 */
+	csay = say;
+	for (y = 0; y < dst->h; y++) {
+	    /*
+	     * Setup color source pointers 
+	     */
+	    c00 = csp;
+	    c01 = csp;
+	    c01++;
+	    c10 = (tColorRGBA *) ((Uint8 *) csp + src->pitch);
+	    c11 = c10;
+	    c11++;
+	    csax = sax;
+	    for (x = 0; x < dst->w; x++) {
+
+		/*
+		 * Interpolate colors 
+		 */
+		ex = (*csax & 0xffff);
+		ey = (*csay & 0xffff);
+		t1 = ((((c01->r - c00->r) * ex) >> 16) + c00->r) & 0xff;
+		t2 = ((((c11->r - c10->r) * ex) >> 16) + c10->r) & 0xff;
+		dp->r = (((t2 - t1) * ey) >> 16) + t1;
+		t1 = ((((c01->g - c00->g) * ex) >> 16) + c00->g) & 0xff;
+		t2 = ((((c11->g - c10->g) * ex) >> 16) + c10->g) & 0xff;
+		dp->g = (((t2 - t1) * ey) >> 16) + t1;
+		t1 = ((((c01->b - c00->b) * ex) >> 16) + c00->b) & 0xff;
+		t2 = ((((c11->b - c10->b) * ex) >> 16) + c10->b) & 0xff;
+		dp->b = (((t2 - t1) * ey) >> 16) + t1;
+		t1 = ((((c01->a - c00->a) * ex) >> 16) + c00->a) & 0xff;
+		t2 = ((((c11->a - c10->a) * ex) >> 16) + c10->a) & 0xff;
+		dp->a = (((t2 - t1) * ey) >> 16) + t1;
+
+		/*
+		 * Advance source pointers 
+		 */
+		csax++;
+		sstep = (*csax >> 16);
+		c00 += sstep;
+		c01 += sstep;
+		c10 += sstep;
+		c11 += sstep;
+		/*
+		 * Advance destination pointer 
+		 */
+		dp++;
+	    }
+	    /*
+	     * Advance source pointer 
+	     */
+	    csay++;
+	    csp = (tColorRGBA *) ((Uint8 *) csp + (*csay >> 16) * src->pitch);
+	    /*
+	     * Advance destination pointers 
+	     */
+	    dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
+	}
+
+    } else {
+
+	/*
+	 * Non-Interpolating Zoom 
+	 */
+
+	csay = say;
+	for (y = 0; y < dst->h; y++) {
+	    sp = csp;
+	    csax = sax;
+	    for (x = 0; x < dst->w; x++) {
+		/*
+		 * Draw 
+		 */
+		*dp = *sp;
+		/*
+		 * Advance source pointers 
+		 */
+		csax++;
+		sstep = (*csax >> 16);
+		if (flipx) sstep = -sstep;
+		sp += sstep;
+		/*
+		 * Advance destination pointer 
+		 */
+		dp++;
+	    }
+	    /*
+	     * Advance source pointer 
+	     */
+	    csay++;
+	    sstep = (*csay >> 16) * src->pitch;
+	    if (flipy) sstep = -sstep;
+	    csp = (tColorRGBA *) ((Uint8 *) csp + sstep);
+
+	    /*
+	     * Advance destination pointers 
+	     */
+	    dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
+	}
+
+    }
+
+    /*
+     * Remove temp arrays 
+     */
+    free(sax);
+    free(say);
+
+    return (0);
+}
+
+/* 
+ 
+ 8bit Zoomer without smoothing.
+
+ Zoomes 8bit palette/Y 'src' surface to 'dst' surface.
+ 
+*/
+
+int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst, int flipx, int flipy)
+{
+    Uint32 x, y, sx, sy, *sax, *say, *csax, *csay, csx, csy;
+    Uint8 *sp, *dp, *csp;
+    int dgap;
+
+    /*
+     * Variable setup 
+     */
+    sx = (Uint32) (65536.0 * (float) src->w / (float) dst->w);
+    sy = (Uint32) (65536.0 * (float) src->h / (float) dst->h);
+
+    /*
+     * Allocate memory for row increments 
+     */
+    if ((sax = (Uint32 *) malloc(dst->w * sizeof(Uint32))) == NULL) {
+	return (-1);
+    }
+    if ((say = (Uint32 *) malloc(dst->h * sizeof(Uint32))) == NULL) {
+	if (sax != NULL) {
+	    free(sax);
+	}
+	return (-1);
+    }
+
+    /*
+     * Precalculate row increments 
+     */
+    csx = 0;
+    csax = sax;
+    for (x = 0; x < dst->w; x++) {
+	csx += sx;
+	*csax = (csx >> 16);
+	csx &= 0xffff;
+	csax++;
+    }
+    csy = 0;
+    csay = say;
+    for (y = 0; y < dst->h; y++) {
+	csy += sy;
+	*csay = (csy >> 16);
+	csy &= 0xffff;
+	csay++;
+    }
+
+    csx = 0;
+    csax = sax;
+    for (x = 0; x < dst->w; x++) {
+	csx += (*csax);
+	csax++;
+    }
+    csy = 0;
+    csay = say;
+    for (y = 0; y < dst->h; y++) {
+	csy += (*csay);
+	csay++;
+    }
+
+    /*
+     * Pointer setup 
+     */
+    sp = csp = (Uint8 *) src->pixels;
+    dp = (Uint8 *) dst->pixels;
+    dgap = dst->pitch - dst->w;
+
+    /*
+     * Draw 
+     */
+    csay = say;
+    for (y = 0; y < dst->h; y++) {
+	csax = sax;
+	sp = csp;
+	for (x = 0; x < dst->w; x++) {
+	    /*
+	     * Draw 
+	     */
+	    *dp = *sp;
+	    /*
+	     * Advance source pointers 
+	     */
+	    sp += (*csax);
+	    csax++;
+	    /*
+	     * Advance destination pointer 
+	     */
+	    dp++;
+	}
+	/*
+	 * Advance source pointer (for row) 
+	 */
+	csp += ((*csay) * src->pitch);
+	csay++;
+	/*
+	 * Advance destination pointers 
+	 */
+	dp += dgap;
+    }
+
+    /*
+     * Remove temp arrays 
+     */
+    free(sax);
+    free(say);
+
+    return (0);
+}
+
+/* 
+ 
+ 32bit Rotozoomer with optional anti-aliasing by bilinear interpolation.
+
+ Rotates and zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface.
+ 
+*/
+
+void transformSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int cx, int cy, int isin, int icos, int flipx, int flipy, int smooth)
+{
+    int x, y, t1, t2, dx, dy, xd, yd, sdx, sdy, ax, ay, ex, ey, sw, sh;
+    tColorRGBA c00, c01, c10, c11;
+    tColorRGBA *pc, *sp;
+    int gap;
+
+    /*
+     * Variable setup 
+     */
+    xd = ((src->w - dst->w) << 15);
+    yd = ((src->h - dst->h) << 15);
+    ax = (cx << 16) - (icos * cx);
+    ay = (cy << 16) - (isin * cx);
+    sw = src->w - 1;
+    sh = src->h - 1;
+    pc = dst->pixels;
+    gap = dst->pitch - dst->w * 4;
+
+    /*
+     * Switch between interpolating and non-interpolating code 
+     */
+    if (smooth) {
+	for (y = 0; y < dst->h; y++) {
+	    dy = cy - y;
+	    sdx = (ax + (isin * dy)) + xd;
+	    sdy = (ay - (icos * dy)) + yd;
+	    for (x = 0; x < dst->w; x++) {
+		dx = (sdx >> 16);
+		dy = (sdy >> 16);
+		if ((dx >= -1) && (dy >= -1) && (dx < src->w) && (dy < src->h)) {
+		    if ((dx >= 0) && (dy >= 0) && (dx < sw) && (dy < sh)) {
+			sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy);
+			sp += dx;
+			c00 = *sp;
+			sp += 1;
+			c01 = *sp;
+			sp = (tColorRGBA *) ((Uint8 *) sp + src->pitch);
+			sp -= 1;
+			c10 = *sp;
+			sp += 1;
+			c11 = *sp;
+		    } else if ((dx == sw) && (dy == sh)) {
+			sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy);
+			sp += dx;
+			c00 = *sp;
+			c01 = *sp;
+			c10 = *sp;
+			c11 = *sp;
+		    } else if ((dx == -1) && (dy == -1)) {
+			sp = (tColorRGBA *) (src->pixels);
+			c00 = *sp;
+			c01 = *sp;
+			c10 = *sp;
+			c11 = *sp;
+		    } else if ((dx == -1) && (dy == sh)) {
+			sp = (tColorRGBA *) (src->pixels);
+			sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy);
+			c00 = *sp;
+			c01 = *sp;
+			c10 = *sp;
+			c11 = *sp;
+		    } else if ((dx == sw) && (dy == -1)) {
+			sp = (tColorRGBA *) (src->pixels);
+			sp += dx;
+			c00 = *sp;
+			c01 = *sp;
+			c10 = *sp;
+			c11 = *sp;
+		    } else if (dx == -1) {
+			sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy);
+			c00 = *sp;
+			c01 = *sp;
+			c10 = *sp;
+			sp = (tColorRGBA *) ((Uint8 *) sp + src->pitch);
+			c11 = *sp;
+		    } else if (dy == -1) {
+			sp = (tColorRGBA *) (src->pixels);
+			sp += dx;
+			c00 = *sp;
+			c01 = *sp;
+			c10 = *sp;
+			sp += 1;
+			c11 = *sp;
+		    } else if (dx == sw) {
+			sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy);
+			sp += dx;
+			c00 = *sp;
+			c01 = *sp;
+			sp = (tColorRGBA *) ((Uint8 *) sp + src->pitch);
+			c10 = *sp;
+			c11 = *sp;
+		    } else if (dy == sh) {
+			sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy);
+			sp += dx;
+			c00 = *sp;
+			sp += 1;
+			c01 = *sp;
+			c10 = *sp;
+			c11 = *sp;
+		    }
+		    /*
+		     * Interpolate colors 
+		     */
+		    ex = (sdx & 0xffff);
+		    ey = (sdy & 0xffff);
+		    t1 = ((((c01.r - c00.r) * ex) >> 16) + c00.r) & 0xff;
+		    t2 = ((((c11.r - c10.r) * ex) >> 16) + c10.r) & 0xff;
+		    pc->r = (((t2 - t1) * ey) >> 16) + t1;
+		    t1 = ((((c01.g - c00.g) * ex) >> 16) + c00.g) & 0xff;
+		    t2 = ((((c11.g - c10.g) * ex) >> 16) + c10.g) & 0xff;
+		    pc->g = (((t2 - t1) * ey) >> 16) + t1;
+		    t1 = ((((c01.b - c00.b) * ex) >> 16) + c00.b) & 0xff;
+		    t2 = ((((c11.b - c10.b) * ex) >> 16) + c10.b) & 0xff;
+		    pc->b = (((t2 - t1) * ey) >> 16) + t1;
+		    t1 = ((((c01.a - c00.a) * ex) >> 16) + c00.a) & 0xff;
+		    t2 = ((((c11.a - c10.a) * ex) >> 16) + c10.a) & 0xff;
+		    pc->a = (((t2 - t1) * ey) >> 16) + t1;
+		}
+		sdx += icos;
+		sdy += isin;
+		pc++;
+	    }
+	    pc = (tColorRGBA *) ((Uint8 *) pc + gap);
+	}
+    } else {
+	for (y = 0; y < dst->h; y++) {
+	    dy = cy - y;
+	    sdx = (ax + (isin * dy)) + xd;
+	    sdy = (ay - (icos * dy)) + yd;
+	    for (x = 0; x < dst->w; x++) {
+		dx = (short) (sdx >> 16);
+		dy = (short) (sdy >> 16);
+		if (flipx) dx = (src->w-1)-dx;
+		if (flipy) dy = (src->h-1)-dy;
+		if ((dx >= 0) && (dy >= 0) && (dx < src->w) && (dy < src->h)) {
+		    sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy);
+		    sp += dx;
+		    *pc = *sp;
+		}
+		sdx += icos;
+		sdy += isin;
+		pc++;
+	    }
+	    pc = (tColorRGBA *) ((Uint8 *) pc + gap);
+	}
+    }
+}
+
+/* 
+ 
+ 8bit Rotozoomer without smoothing
+
+ Rotates and zoomes 8bit palette/Y 'src' surface to 'dst' surface.
+ 
+*/
+
+void transformSurfaceY(SDL_Surface * src, SDL_Surface * dst, int cx, int cy, int isin, int icos)
+{
+    int x, y, dx, dy, xd, yd, sdx, sdy, ax, ay, sw, sh;
+    tColorY *pc, *sp;
+    int gap;
+
+    /*
+     * Variable setup 
+     */
+    xd = ((src->w - dst->w) << 15);
+    yd = ((src->h - dst->h) << 15);
+    ax = (cx << 16) - (icos * cx);
+    ay = (cy << 16) - (isin * cx);
+    sw = src->w - 1;
+    sh = src->h - 1;
+    pc = dst->pixels;
+    gap = dst->pitch - dst->w;
+    /*
+     * Clear surface to colorkey 
+     */
+    memset(pc, (unsigned char) (src->format->colorkey & 0xff), dst->pitch * dst->h);
+    /*
+     * Iterate through destination surface 
+     */
+    for (y = 0; y < dst->h; y++) {
+	dy = cy - y;
+	sdx = (ax + (isin * dy)) + xd;
+	sdy = (ay - (icos * dy)) + yd;
+	for (x = 0; x < dst->w; x++) {
+	    dx = (short) (sdx >> 16);
+	    dy = (short) (sdy >> 16);
+	    if ((dx >= 0) && (dy >= 0) && (dx < src->w) && (dy < src->h)) {
+		sp = (tColorY *) (src->pixels);
+		sp += (src->pitch * dy + dx);
+		*pc = *sp;
+	    }
+	    sdx += icos;
+	    sdy += isin;
+	    pc++;
+	}
+	pc += gap;
+    }
+}
+
+
+/* 
+ 
+ 32bit specialized 90degree rotator
+
+ Rotates and zooms 'src' surface to 'dst' surface in 90degree increments.
+
+ (contributed by Jeff Schiller)
+ 
+*/
+SDL_Surface* rotateSurface90Degrees(SDL_Surface* pSurf, int numClockwiseTurns) 
+{
+ int row, col;
+ 
+ // Has to be a valid surface pointer and only 32-bit surfaces (for now)
+ if (!pSurf || pSurf->format->BitsPerPixel != 32) { return NULL; }
+
+ // normalize numClockwiseTurns
+ while(numClockwiseTurns < 0) { numClockwiseTurns += 4; }
+ numClockwiseTurns = (numClockwiseTurns % 4);
+
+ // if it's even, our new width will be the same as the source surface
+ int newWidth = (numClockwiseTurns % 2) ? (pSurf->h) : (pSurf->w);
+ int newHeight = (numClockwiseTurns % 2) ? (pSurf->w) : (pSurf->h);
+ SDL_Surface* pSurfOut = SDL_CreateRGBSurface( pSurf->flags, newWidth, newHeight, pSurf->format->BitsPerPixel,
+                           pSurf->format->Rmask,
+                           pSurf->format->Gmask, 
+                           pSurf->format->Bmask, 
+                           pSurf->format->Amask);
+ if(!pSurfOut) {
+   return NULL;
+ }
+
+ if(numClockwiseTurns != 0) {
+   SDL_LockSurface(pSurf);
+   SDL_LockSurface(pSurfOut);
+   switch(numClockwiseTurns) {
+     // rotate clockwise
+     case 1: // rotated 90 degrees clockwise
+     {
+       Uint32* srcBuf = NULL;
+       Uint32* dstBuf = NULL;
+
+       for (row = 0; row < pSurf->h; ++row) {
+         srcBuf = (Uint32*)(pSurf->pixels) + (row*pSurf->pitch/4);
+         dstBuf = (Uint32*)(pSurfOut->pixels) + (pSurfOut->w - row - 1);
+         for (col = 0; col < pSurf->w; ++col) {
+           *dstBuf = *srcBuf;
+           ++srcBuf;
+           dstBuf += pSurfOut->pitch/4;
+         } // for(col)
+       } // for(row)
+     }
+     break;
+
+     case 2: // rotated 180 degrees clockwise
+     {
+       Uint32* srcBuf = NULL;
+       Uint32* dstBuf = NULL;
+
+       for(row = 0; row < pSurf->h; ++row) {
+         srcBuf = (Uint32*)(pSurf->pixels) + (row*pSurf->pitch/4);
+         dstBuf = (Uint32*)(pSurfOut->pixels) + ((pSurfOut->h - row - 1)*pSurfOut->pitch/4) + (pSurfOut->w - 1);
+         for(col = 0; col < pSurf->w; ++col) {
+           *dstBuf = *srcBuf;
+           ++srcBuf;
+           --dstBuf;
+         } // for(col)
+       } // for(row)
+     }
+     break;
+
+     case 3:
+     {
+       Uint32* srcBuf = NULL;
+       Uint32* dstBuf = NULL;
+
+       for(row = 0; row < pSurf->h; ++row) {
+         srcBuf = (Uint32*)(pSurf->pixels) + (row*pSurf->pitch/4);
+         dstBuf = (Uint32*)(pSurfOut->pixels) + row + ((pSurfOut->h - 1)*pSurfOut->pitch/4);
+         for(col = 0; col < pSurf->w; ++col) {
+           *dstBuf = *srcBuf;
+           ++srcBuf;
+           dstBuf -= pSurfOut->pitch/4;
+         } // for(col)
+       } // for(row)
+     }
+     break;
+   } // switch
+
+   SDL_UnlockSurface(pSurf);
+   SDL_UnlockSurface(pSurfOut);
+ } // if numClockwiseTurns > 0
+ else {
+   // simply copy surface to output
+   if(SDL_BlitSurface(pSurf, NULL, pSurfOut, NULL)) {
+     return NULL;
+   }
+ }
+ return pSurfOut;
+}
+
+/* 
+ 
+ rotozoomSurface()
+
+ Rotates and zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface.
+ 'angle' is the rotation in degrees. 'zoom' a scaling factor. If 'smooth' is 1
+ then the destination 32bit surface is anti-aliased. If the surface is not 8bit
+ or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly.
+
+*/
+
+#define VALUE_LIMIT	0.001
+
+
+/* Local rotozoom-size function with trig result return */
+
+void rotozoomSurfaceSizeTrig(int width, int height, double angle, double zoomx, double zoomy, int *dstwidth, int *dstheight, 
+			     double *canglezoom, double *sanglezoom)
+{
+    double x, y, cx, cy, sx, sy;
+    double radangle;
+    int dstwidthhalf, dstheighthalf;
+
+    /*
+     * Determine destination width and height by rotating a centered source box 
+     */
+    radangle = angle * (M_PI / 180.0);
+    *sanglezoom = sin(radangle);
+    *canglezoom = cos(radangle);
+    *sanglezoom *= zoomx;
+    *canglezoom *= zoomx;
+    x = width / 2;
+    y = height / 2;
+    cx = *canglezoom * x;
+    cy = *canglezoom * y;
+    sx = *sanglezoom * x;
+    sy = *sanglezoom * y;
+    
+    dstwidthhalf = MAX((int)
+		       ceil(MAX(MAX(MAX(fabs(cx + sy), fabs(cx - sy)), fabs(-cx + sy)), fabs(-cx - sy))), 1);
+    dstheighthalf = MAX((int)
+			ceil(MAX(MAX(MAX(fabs(sx + cy), fabs(sx - cy)), fabs(-sx + cy)), fabs(-sx - cy))), 1);
+    *dstwidth = 2 * dstwidthhalf;
+    *dstheight = 2 * dstheighthalf;
+}
+
+
+/* Publically available rotozoom-size function */
+
+void rotozoomSurfaceSizeXY(int width, int height, double angle, double zoomx, double zoomy, int *dstwidth, int *dstheight)
+{
+    double dummy_sanglezoom, dummy_canglezoom;
+
+    rotozoomSurfaceSizeTrig(width, height, angle, zoomx, zoomy, dstwidth, dstheight, &dummy_sanglezoom, &dummy_canglezoom);
+}
+
+/* Publically available rotozoom-size function */
+
+void rotozoomSurfaceSize(int width, int height, double angle, double zoom, int *dstwidth, int *dstheight)
+{
+    double dummy_sanglezoom, dummy_canglezoom;
+
+    rotozoomSurfaceSizeTrig(width, height, angle, zoom, zoom, dstwidth, dstheight, &dummy_sanglezoom, &dummy_canglezoom);
+}
+
+/* Publically available rotozoom function */
+
+SDL_Surface *rotozoomSurface(SDL_Surface * src, double angle, double zoom, int smooth)
+{
+  return rotozoomSurfaceXY(src, angle, zoom, zoom, smooth);
+}
+
+/* Publically available rotozoom function */
+
+SDL_Surface *rotozoomSurfaceXY(SDL_Surface * src, double angle, double zoomx, double zoomy, int smooth)
+{
+    SDL_Surface *rz_src;
+    SDL_Surface *rz_dst;
+    double zoominv;
+    double sanglezoom, canglezoom, sanglezoominv, canglezoominv;
+    int dstwidthhalf, dstwidth, dstheighthalf, dstheight;
+    int is32bit;
+    int i, src_converted;
+    int flipx,flipy;
+
+    /*
+     * Sanity check 
+     */
+    if (src == NULL)
+	return (NULL);
+
+    /*
+     * Determine if source surface is 32bit or 8bit 
+     */
+    is32bit = (src->format->BitsPerPixel == 32);
+    if ((is32bit) || (src->format->BitsPerPixel == 8)) {
+	/*
+	 * Use source surface 'as is' 
+	 */
+	rz_src = src;
+	src_converted = 0;
+    } else {
+	/*
+	 * New source surface is 32bit with a defined RGBA ordering 
+	 */
+	rz_src =
+	    SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h, 32, 
+#if SDL_BYTEORDER == SDL_LIL_ENDIAN
+                                0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
+#else
+                                0xff000000,  0x00ff0000, 0x0000ff00, 0x000000ff
+#endif
+	    );
+	SDL_BlitSurface(src, NULL, rz_src, NULL);
+	src_converted = 1;
+	is32bit = 1;
+    }
+
+    /*
+     * Sanity check zoom factor 
+     */
+    flipx = (zoomx<0);
+    if (flipx) zoomx=-zoomx;
+    flipy = (zoomy<0);
+    if (flipy) zoomy=-zoomy;
+    if (zoomx < VALUE_LIMIT) zoomx = VALUE_LIMIT;
+    if (zoomy < VALUE_LIMIT) zoomy = VALUE_LIMIT;
+    zoominv = 65536.0 / (zoomx * zoomx);
+
+    /*
+     * Check if we have a rotozoom or just a zoom 
+     */
+    if (fabs(angle) > VALUE_LIMIT) {
+
+	/*
+	 * Angle!=0: full rotozoom 
+	 */
+	/*
+	 * ----------------------- 
+	 */
+
+	/* Determine target size */
+	rotozoomSurfaceSizeTrig(rz_src->w, rz_src->h, angle, zoomx, zoomy, &dstwidth, &dstheight, &canglezoom, &sanglezoom);
+
+	/*
+	 * Calculate target factors from sin/cos and zoom 
+	 */
+	sanglezoominv = sanglezoom;
+	canglezoominv = canglezoom;
+	sanglezoominv *= zoominv;
+	canglezoominv *= zoominv;
+
+	/* Calculate half size */
+	dstwidthhalf = dstwidth / 2;
+	dstheighthalf = dstheight / 2;
+
+	/*
+	 * Alloc space to completely contain the rotated surface 
+	 */
+	rz_dst = NULL;
+	if (is32bit) {
+	    /*
+	     * Target surface is 32bit with source RGBA/ABGR ordering 
+	     */
+	    rz_dst =
+		SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 32,
+				     rz_src->format->Rmask, rz_src->format->Gmask,
+				     rz_src->format->Bmask, rz_src->format->Amask);
+	} else {
+	    /*
+	     * Target surface is 8bit 
+	     */
+	    rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 8, 0, 0, 0, 0);
+	}
+
+	/*
+	 * Lock source surface 
+	 */
+	SDL_LockSurface(rz_src);
+	/*
+	 * Check which kind of surface we have 
+	 */
+	if (is32bit) {
+	    /*
+	     * Call the 32bit transformation routine to do the rotation (using alpha) 
+	     */
+	    transformSurfaceRGBA(rz_src, rz_dst, dstwidthhalf, dstheighthalf,
+				 (int) (sanglezoominv), (int) (canglezoominv), 
+				 flipx, flipy,
+				 smooth);
+	    /*
+	     * Turn on source-alpha support 
+	     */
+	    SDL_SetAlpha(rz_dst, SDL_SRCALPHA, 255);
+	} else {
+	    /*
+	     * Copy palette and colorkey info 
+	     */
+	    for (i = 0; i < rz_src->format->palette->ncolors; i++) {
+		rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i];
+	    }
+	    rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors;
+	    /*
+	     * Call the 8bit transformation routine to do the rotation 
+	     */
+	    transformSurfaceY(rz_src, rz_dst, dstwidthhalf, dstheighthalf,
+			      (int) (sanglezoominv), (int) (canglezoominv));
+	    SDL_SetColorKey(rz_dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, rz_src->format->colorkey);
+	}
+	/*
+	 * Unlock source surface 
+	 */
+	SDL_UnlockSurface(rz_src);
+
+    } else {
+
+	/*
+	 * Angle=0: Just a zoom 
+	 */
+	/*
+	 * -------------------- 
+	 */
+
+	/*
+	 * Calculate target size
+	 */
+	zoomSurfaceSize(rz_src->w, rz_src->h, zoomx, zoomy, &dstwidth, &dstheight);
+
+	/*
+	 * Alloc space to completely contain the zoomed surface 
+	 */
+	rz_dst = NULL;
+	if (is32bit) {
+	    /*
+	     * Target surface is 32bit with source RGBA/ABGR ordering 
+	     */
+	    rz_dst =
+		SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 32,
+				     rz_src->format->Rmask, rz_src->format->Gmask,
+				     rz_src->format->Bmask, rz_src->format->Amask);
+	} else {
+	    /*
+	     * Target surface is 8bit 
+	     */
+	    rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 8, 0, 0, 0, 0);
+	}
+
+	/*
+	 * Lock source surface 
+	 */
+	SDL_LockSurface(rz_src);
+	/*
+	 * Check which kind of surface we have 
+	 */
+	if (is32bit) {
+	    /*
+	     * Call the 32bit transformation routine to do the zooming (using alpha) 
+	     */
+	    zoomSurfaceRGBA(rz_src, rz_dst, flipx, flipy, smooth);
+	    /*
+	     * Turn on source-alpha support 
+	     */
+	    SDL_SetAlpha(rz_dst, SDL_SRCALPHA, 255);
+	} else {
+	    /*
+	     * Copy palette and colorkey info 
+	     */
+	    for (i = 0; i < rz_src->format->palette->ncolors; i++) {
+		rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i];
+	    }
+	    rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors;
+	    /*
+	     * Call the 8bit transformation routine to do the zooming 
+	     */
+	    zoomSurfaceY(rz_src, rz_dst, flipx, flipy);
+	    SDL_SetColorKey(rz_dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, rz_src->format->colorkey);
+	}
+	/*
+	 * Unlock source surface 
+	 */
+	SDL_UnlockSurface(rz_src);
+    }
+
+    /*
+     * Cleanup temp surface 
+     */
+    if (src_converted) {
+	SDL_FreeSurface(rz_src);
+    }
+
+    /*
+     * Return destination surface 
+     */
+    return (rz_dst);
+}
+
+/* 
+ 
+ zoomSurface()
+
+ Zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface.
+ 'zoomx' and 'zoomy' are scaling factors for width and height. If 'smooth' is 1
+ then the destination 32bit surface is anti-aliased. If the surface is not 8bit
+ or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly.
+
+*/
+
+#define VALUE_LIMIT	0.001
+
+void zoomSurfaceSize(int width, int height, double zoomx, double zoomy, int *dstwidth, int *dstheight)
+{
+    /*
+     * Sanity check zoom factors 
+     */
+    if (zoomx < VALUE_LIMIT) {
+	zoomx = VALUE_LIMIT;
+    }
+    if (zoomy < VALUE_LIMIT) {
+	zoomy = VALUE_LIMIT;
+    }
+
+    /*
+     * Calculate target size 
+     */
+    *dstwidth = (int) ((double) width * zoomx);
+    *dstheight = (int) ((double) height * zoomy);
+    if (*dstwidth < 1) {
+	*dstwidth = 1;
+    }
+    if (*dstheight < 1) {
+	*dstheight = 1;
+    }
+}
+
+SDL_Surface *zoomSurface(SDL_Surface * src, double zoomx, double zoomy, int smooth)
+{
+    SDL_Surface *rz_src;
+    SDL_Surface *rz_dst;
+    int dstwidth, dstheight;
+    int is32bit;
+    int i, src_converted;
+    int flipx, flipy;
+
+    /*
+     * Sanity check 
+     */
+    if (src == NULL)
+	return (NULL);
+
+    /*
+     * Determine if source surface is 32bit or 8bit 
+     */
+    is32bit = (src->format->BitsPerPixel == 32);
+    if ((is32bit) || (src->format->BitsPerPixel == 8)) {
+	/*
+	 * Use source surface 'as is' 
+	 */
+	rz_src = src;
+	src_converted = 0;
+    } else {
+	/*
+	 * New source surface is 32bit with a defined RGBA ordering 
+	 */
+	rz_src =
+	    SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h, 32, 
+#if SDL_BYTEORDER == SDL_LIL_ENDIAN
+                                0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
+#else
+                                0xff000000,  0x00ff0000, 0x0000ff00, 0x000000ff
+#endif
+	    );
+	SDL_BlitSurface(src, NULL, rz_src, NULL);
+	src_converted = 1;
+	is32bit = 1;
+    }
+
+    flipx = (zoomx<0);
+    if (flipx) zoomx = -zoomx;
+    flipy = (zoomy<0);
+    if (flipy) zoomy = -zoomy;
+
+    /* Get size if target */
+    zoomSurfaceSize(rz_src->w, rz_src->h, zoomx, zoomy, &dstwidth, &dstheight);
+
+    /*
+     * Alloc space to completely contain the zoomed surface 
+     */
+    rz_dst = NULL;
+    if (is32bit) {
+	/*
+	 * Target surface is 32bit with source RGBA/ABGR ordering 
+	 */
+	rz_dst =
+	    SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 32,
+				 rz_src->format->Rmask, rz_src->format->Gmask,
+				 rz_src->format->Bmask, rz_src->format->Amask);
+    } else {
+	/*
+	 * Target surface is 8bit 
+	 */
+	rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 8, 0, 0, 0, 0);
+    }
+
+    /*
+     * Lock source surface 
+     */
+    SDL_LockSurface(rz_src);
+    /*
+     * Check which kind of surface we have 
+     */
+    if (is32bit) {
+	/*
+	 * Call the 32bit transformation routine to do the zooming (using alpha) 
+	 */
+	zoomSurfaceRGBA(rz_src, rz_dst, flipx, flipy, smooth);
+	/*
+	 * Turn on source-alpha support 
+	 */
+	SDL_SetAlpha(rz_dst, SDL_SRCALPHA, 255);
+    } else {
+	/*
+	 * Copy palette and colorkey info 
+	 */
+	for (i = 0; i < rz_src->format->palette->ncolors; i++) {
+	    rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i];
+	}
+	rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors;
+	/*
+	 * Call the 8bit transformation routine to do the zooming 
+	 */
+	zoomSurfaceY(rz_src, rz_dst, flipx, flipy);
+	SDL_SetColorKey(rz_dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, rz_src->format->colorkey);
+    }
+    /*
+     * Unlock source surface 
+     */
+    SDL_UnlockSurface(rz_src);
+
+    /*
+     * Cleanup temp surface 
+     */
+    if (src_converted) {
+	SDL_FreeSurface(rz_src);
+    }
+
+    /*
+     * Return destination surface 
+     */
+    return (rz_dst);
+}
+
+SDL_Surface *shrinkSurface(SDL_Surface * src, int factorx, int factory)
+{
+    SDL_Surface *rz_src;
+    SDL_Surface *rz_dst;
+    int dstwidth, dstheight;
+    int is32bit;
+    int i, src_converted;
+
+    /*
+     * Sanity check 
+     */
+    if (src == NULL)
+	return (NULL);
+
+    /*
+     * Determine if source surface is 32bit or 8bit 
+     */
+    is32bit = (src->format->BitsPerPixel == 32);
+    if ((is32bit) || (src->format->BitsPerPixel == 8)) {
+	/*
+	 * Use source surface 'as is' 
+	 */
+	rz_src = src;
+	src_converted = 0;
+    } else {
+	/*
+	 * New source surface is 32bit with a defined RGBA ordering 
+	 */
+	rz_src =
+	    SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h, 32, 
+#if SDL_BYTEORDER == SDL_LIL_ENDIAN
+                                0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
+#else
+                                0xff000000,  0x00ff0000, 0x0000ff00, 0x000000ff
+#endif
+	    );
+	SDL_BlitSurface(src, NULL, rz_src, NULL);
+	src_converted = 1;
+	is32bit = 1;
+    }
+
+    /* Get size for target */
+    dstwidth=rz_src->w/factorx;
+    while (dstwidth*factorx>rz_src->w) { dstwidth--; }
+    dstheight=rz_src->h/factory;
+    while (dstheight*factory>rz_src->h) { dstheight--; }
+
+    /*
+     * Alloc space to completely contain the shrunken surface 
+     */
+    rz_dst = NULL;
+    if (is32bit) {
+	/*
+	 * Target surface is 32bit with source RGBA/ABGR ordering 
+	 */
+	rz_dst =
+	    SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 32,
+				 rz_src->format->Rmask, rz_src->format->Gmask,
+				 rz_src->format->Bmask, rz_src->format->Amask);
+    } else {
+	/*
+	 * Target surface is 8bit 
+	 */
+	rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 8, 0, 0, 0, 0);
+    }
+
+    /*
+     * Lock source surface 
+     */
+    SDL_LockSurface(rz_src);
+    /*
+     * Check which kind of surface we have 
+     */
+    if (is32bit) {
+	/*
+	 * Call the 32bit transformation routine to do the shrinking (using alpha) 
+	 */
+	shrinkSurfaceRGBA(rz_src, rz_dst, factorx, factory);
+	/*
+	 * Turn on source-alpha support 
+	 */
+	SDL_SetAlpha(rz_dst, SDL_SRCALPHA, 255);
+    } else {
+	/*
+	 * Copy palette and colorkey info 
+	 */
+	for (i = 0; i < rz_src->format->palette->ncolors; i++) {
+	    rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i];
+	}
+	rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors;
+	/*
+	 * Call the 8bit transformation routine to do the shrinking 
+	 */
+	shrinkSurfaceY(rz_src, rz_dst, factorx, factory);
+	SDL_SetColorKey(rz_dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, rz_src->format->colorkey);
+    }
+    /*
+     * Unlock source surface 
+     */
+    SDL_UnlockSurface(rz_src);
+
+    /*
+     * Cleanup temp surface 
+     */
+    if (src_converted) {
+	SDL_FreeSurface(rz_src);
+    }
+
+    /*
+     * Return destination surface 
+     */
+    return (rz_dst);
+}

Added: tuxmath/trunk/src/SDL_rotozoom.h
===================================================================
--- tuxmath/trunk/src/SDL_rotozoom.h	                        (rev 0)
+++ tuxmath/trunk/src/SDL_rotozoom.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,132 @@
+
+/*
+
+  The SDL_rotozoom sources were copied from the SDL_gfx library and
+  are relicensed, only for the purposes of TuxMath, to GPL.  Thanks to
+  Andreas Schiffler.
+
+ SDL_rotozoom - rotozoomer
+
+ LGPL (c) A. Schiffler
+
+*/
+
+#ifndef _SDL_rotozoom_h
+#define _SDL_rotozoom_h
+
+#include <math.h>
+
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef M_PI
+#define M_PI	3.141592654
+#endif
+
+#include "SDL.h"
+
+/* ---- Defines */
+
+#define SMOOTHING_OFF		0
+#define SMOOTHING_ON		1
+
+/* ---- Structures */
+
+    typedef struct tColorRGBA {
+	Uint8 r;
+	Uint8 g;
+	Uint8 b;
+	Uint8 a;
+    } tColorRGBA;
+
+    typedef struct tColorY {
+	Uint8 y;
+    } tColorY;
+
+
+/* ---- Prototypes */
+
+// #ifdef WIN32
+// #ifdef BUILD_DLL
+// #define DLLINTERFACE __declspec(dllexport)
+// #else
+// #define DLLINTERFACE __declspec(dllimport)
+// #endif
+// #else
+// #define DLLINTERFACE
+// #endif
+
+/* NOTE inactivating above declspec stuff because we are building */
+/* for our own tree                                               */
+#define DLLINTERFACE
+/* 
+ 
+ rotozoomSurface()
+
+ Rotates and zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface.
+ 'angle' is the rotation in degrees. 'zoom' a scaling factor. If 'smooth' is 1
+ then the destination 32bit surface is anti-aliased. If the surface is not 8bit
+ or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly.
+
+*/
+
+    DLLINTERFACE SDL_Surface *rotozoomSurface(SDL_Surface * src, double angle, double zoom, int smooth);
+
+    DLLINTERFACE SDL_Surface *rotozoomSurfaceXY
+    (SDL_Surface * src, double angle, double zoomx, double zoomy, int smooth);
+
+/* Returns the size of the target surface for a rotozoomSurface() call */
+
+    DLLINTERFACE void rotozoomSurfaceSize(int width, int height, double angle, double zoom, int *dstwidth,
+					  int *dstheight);
+
+    DLLINTERFACE void rotozoomSurfaceSizeXY
+    (int width, int height, double angle, double zoomx, double zoomy, 
+     int *dstwidth, int *dstheight);
+
+/* 
+ 
+ zoomSurface()
+
+ Zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface.
+ 'zoomx' and 'zoomy' are scaling factors for width and height. If 'smooth' is 1
+ then the destination 32bit surface is anti-aliased. If the surface is not 8bit
+ or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly.
+
+*/
+
+    DLLINTERFACE SDL_Surface *zoomSurface(SDL_Surface * src, double zoomx, double zoomy, int smooth);
+
+/* Returns the size of the target surface for a zoomSurface() call */
+
+    DLLINTERFACE void zoomSurfaceSize(int width, int height, double zoomx, double zoomy, int *dstwidth, int *dstheight);
+
+
+/* 
+    shrinkSurface()
+
+    Shrinks a 32bit or 8bit 'src' surface ti a newly created 'dst' surface.
+    'factorx' and 'factory' are the shrinking ratios (i.e. 2=1/2 the size,
+    3=1/3 the size, etc.) The destination surface is antialiased by averaging
+    the source box RGBA or Y information. If the surface is not 8bit
+    or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly.
+*/     
+    
+    DLLINTERFACE SDL_Surface *shrinkSurface(SDL_Surface * src, int factorx, int factory);
+
+/* 
+
+    Other functions
+    
+*/
+
+    DLLINTERFACE SDL_Surface* rotateSurface90Degrees(SDL_Surface* pSurf, int numClockwiseTurns);
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+
+#endif				/* _SDL_rotozoom_h */

Added: tuxmath/trunk/src/audio.c
===================================================================
--- tuxmath/trunk/src/audio.c	                        (rev 0)
+++ tuxmath/trunk/src/audio.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,80 @@
+/***************************************************************************
+ -  file: audio.c
+ -  description: this file contains audio related functions
+                            -------------------
+    begin                : Jan 22, 2003
+    copyright            : Sam Hart, Jesse Andrews (C) 2003
+    email                : tuxtype-dev at tux4kids.net
+
+    Modified for use in tuxmath by David Bruce - 2006.
+    email                : <dbruce at tampabay.rr.com>
+                           <tuxmath-devel at lists.sourceforge.net>
+***************************************************************************/
+
+/***************************************************************************
+*                                                                         *
+*   This program is free software; you can redistribute it and/or modify  *
+*   it under the terms of the GNU General Public License as published by  *
+*   the Free Software Foundation; either version 2 of the License, or     *
+*   (at your option) any later version.                                   *
+*                                                                         *
+***************************************************************************/
+
+
+#include "tuxmath.h"
+#include "options.h"   //Needed for Opts_UsingSound()
+#include "titlescreen.h"
+
+Mix_Music *music;
+
+void playsound(int snd)
+{
+#ifndef NOSOUND
+  if (Opts_UsingSound())
+    Mix_PlayChannel(-1, sounds[snd], 0);
+#endif
+}
+
+Mix_Music *defaultMusic = NULL; // holds music for audioMusicLoad/unload
+
+
+
+/* audioMusicLoad attempts to load and play the music file 
+ * Note: loops == -1 means forever
+ */
+void audioMusicLoad(char *musicFilename, int loops)
+{
+  if (!Opts_UsingSound())
+  {
+    return;
+  }
+
+  audioMusicUnload(); // make sure defaultMusic is clear
+  defaultMusic = LoadMusic(musicFilename);
+  Mix_PlayMusic(defaultMusic, loops);
+}
+
+
+/* audioMusicUnload attempts to unload any music data that was
+ * loaded using the audioMusicLoad function
+ */
+void audioMusicUnload( void ) {
+  if (!Opts_UsingSound()) return;
+
+  if ( defaultMusic )
+    Mix_FreeMusic( defaultMusic );
+
+  defaultMusic=NULL;
+}
+
+/* audioMusicPlay attempts to play the passed music data. 
+ * if a music file was loaded using the audioMusicLoad
+ * it will be stopped and unloaded
+ * Note: loops == -1 means forever
+ */
+void audioMusicPlay( Mix_Music *musicData, int loops ) { 
+  if (!Opts_UsingSound()) return;
+
+  audioMusicUnload();        
+  Mix_PlayMusic( musicData, loops );
+}

Added: tuxmath/trunk/src/campaign.c
===================================================================
--- tuxmath/trunk/src/campaign.c	                        (rev 0)
+++ tuxmath/trunk/src/campaign.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,169 @@
+/*
+ * campaign.c - handle TuxMath's 'Mission mode' 
+ * 
+ * Author: B. Luchen
+ */
+ 
+#include "campaign.h"
+#include "tuxmath.h"
+#include "credits.h"
+#include "titlescreen.h"
+#include "game.h"
+#include "fileops.h"
+#include "mathcards.h"
+#include "options.h"
+#include "linewrap.h"
+
+
+void briefPlayer(int stage); //show text introducing the given stage
+void readStageSettings(int stage);
+void readRoundSettings(int stage, int round);
+void showGameOver();
+void showGameWon();
+
+char* stagenames[NUM_STAGES] = {"cadet", "scout", "ranger", "ace", "commando"};
+
+int start_campaign()
+{
+  int i, j;
+  int gameresult = 0, endcampaign = 0;
+  char roundmessage[10];
+  char* endtext[2] = {N_("Congratulations! You win!"), ""};
+  printf("Entering start_campaign()\n");
+  
+  
+  for (i = 0; i < NUM_STAGES; ++i)
+  {
+    printf("Stage %s\n", stagenames[i]);
+    briefPlayer(i);
+    for (j = 1; j <= NUM_ROUNDS; ++j)
+    {
+      printf("Round %d\n", j);
+     
+      //read in settings 
+      read_named_config_file("campaign/campaign");    
+      readStageSettings(i);
+      readRoundSettings(i, j);
+      Opts_SetKeepScore(0);
+          
+      snprintf(roundmessage, 10, "%s %d", N_("Round"), j);
+      game_set_start_message(roundmessage, "", "", "");
+
+      MC_PrintMathOptions(stdout, 0);
+
+      //play!
+      printf("Starting game...\n");
+      gameresult = game();
+      
+      //move on if we've won, game over if not
+      if (gameresult == GAME_OVER_WON)
+        ;
+      else if (gameresult == GAME_OVER_LOST)
+      {
+        showGameOver();
+        endcampaign = 1;
+      }
+      else if (gameresult == GAME_OVER_ERROR)
+      {
+        tmdprintf("Error!\n");
+        endcampaign = 1;
+      }
+#ifndef TESTING_CAMPAIGN
+      else if (gameresult == GAME_OVER_ESCAPE)
+      {
+        tmdprintf("hit escape\n");
+        endcampaign = 1;
+      }
+#endif      
+      else
+      {
+        printf("gameresult = %d\n", gameresult);
+        endcampaign = 0;
+      }
+      
+      if (endcampaign)
+        return 0;
+    }
+      
+    //if we've beaten the last stage, there is no bonus, skip to win sequence
+    if (i == NUM_STAGES - 1)
+    {
+      showGameWon();
+      break;
+    }
+/*    //bonus round
+    readStageSettings(i);
+    readRoundSettings(i, -1);
+    game_set_start_message("Bonus", "", "", "");
+    game();
+*/
+  }
+  scroll_text(endtext, screen->clip_rect, 3);
+  return 0;
+}
+
+void briefPlayer(int stage)
+{
+  SDL_FillRect(screen, NULL, 0);
+  //TransWipe(black, RANDOM_WIPE, 10, 20);
+
+  static char* sprites[] = {
+    "sprites/tux_helmet_yellowd.png",
+    "sprites/tux_helmet_greend.png",
+    "sprites/tux_helmet_blued.png",
+    "sprites/tux_helmet_redd.png",
+    "sprites/tux_helmet_blackd.png"
+  };
+  SDL_Surface* icon = NULL;
+  SDL_Rect textarea = screen->clip_rect;
+  SDL_Surface* loadedsprite = LoadImage(sprites[stage], IMG_REGULAR|IMG_NOT_REQUIRED);
+  
+  if (loadedsprite) //stretch the tiny sprite to 3x
+  {
+    icon = zoom(loadedsprite, loadedsprite->w*3, loadedsprite->h*3);
+    textarea.x = icon->w;
+    textarea.y = icon->h;
+    textarea.w = screen->w - icon->w;
+    textarea.h = screen->h - icon->h;
+  }
+  //show this stage's text
+  tmdprintf("Briefing\n");
+  SDL_BlitSurface(icon, NULL, screen, NULL);
+  linewrap_list(briefings[stage], wrapped_lines, 40, MAX_LINES, MAX_LINEWIDTH);
+  scroll_text(wrapped_lines, textarea, 1);
+  tmdprintf("Finished briefing\n");
+  
+  SDL_FreeSurface(loadedsprite);
+  SDL_FreeSurface(icon);
+}
+
+void readStageSettings(int stage)
+{
+  static char fn[PATH_MAX];
+  snprintf(fn,PATH_MAX, "campaign/%s/%s", stagenames[stage], stagenames[stage]);
+  read_named_config_file(fn);
+}
+
+void readRoundSettings(int stage, int round)
+{
+  static char fn[PATH_MAX];
+  if (round == -1)
+    snprintf(fn, PATH_MAX, "campaign/%s/bonus", stagenames[stage]);
+  else
+    snprintf(fn,PATH_MAX, "campaign/%s/round%d", stagenames[stage], round);
+  read_named_config_file(fn);
+}
+
+void showGameOver()
+{
+  const char* text[2] = {N_("Sorry, try again!"), ""};
+  linewrap_list(text, wrapped_lines, 40, MAX_LINES, MAX_LINEWIDTH);
+  scroll_text(wrapped_lines, screen->clip_rect, 3);
+}
+
+void showGameWon()
+{
+  const char* text[2] = {N_("Mission accomplished. The galaxy is safe!"), ""};
+  linewrap_list(text, wrapped_lines, 40, MAX_LINES, MAX_LINEWIDTH);
+  scroll_text(wrapped_lines, screen->clip_rect, 3);
+}

Added: tuxmath/trunk/src/campaign.h
===================================================================
--- tuxmath/trunk/src/campaign.h	                        (rev 0)
+++ tuxmath/trunk/src/campaign.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,86 @@
+#ifndef CAMPAIGN_H
+#define CAMPAIGN_H
+
+/*
+ * campaign.h - prose and function declarations for TuxMath's 'Mission mode' 
+ * 
+ * Author: B. Luchen
+ */
+ 
+#include "SDL_extras.h"
+#include "tuxmath.h"
+
+
+//#define TESTING_CAMPAIGN //allow ESC to skip missions instead of exiting
+
+#define NUM_STAGES 5 
+#define NUM_ROUNDS 3
+
+/* NOTE this has to be static to be in a header file or it will cause */
+/* multiple definition errors if included in more than one file.      */
+
+/* NOTE: the convention has changed. Use " " for a blank line (note
+   the space), and use "" (rather than NULL) for the termination
+   string. This is a consequence of the linewrapping code.  TEH Feb
+   2009. */
+static const char* briefings[NUM_STAGES][20] = {
+  //cadet
+  {
+    N_("-[Esc] to skip"),
+    N_("Mission One: Careful Cadet"),
+    "--------------------------",
+    N_("I'm so glad you've come!"),
+    " ",
+    N_("The penguins need your help! Comets are falling from the sky, and are melting the penguins' igloos. To save their homes, we need you to find the secret code that will zap each comet."),
+    " ",
+    N_("Do your best!"),
+    ""
+  },
+  //scout
+  {
+    "-[Esc] to skip",
+    N_("Mission Two: Smart Scout"),
+    "------------------------",
+    N_("Great job! Since you saved the penguins' homes, we are promoting you to Scout. Scouts are good for keeping an eye out for trouble..."),
+    " ",
+    N_("...like what's happening right now! The TakeAways have come, and they're sending new, trickier comets against the penguins!"),
+    N_("But you can save them!"),
+    ""
+  },
+  //ranger
+  {
+    "-[Esc] to skip",
+    N_("Mission Three: Royal Ranger"),
+    "---------------------------",
+    N_("You've done it again! The Penguin Emperor has chosen you to join his team of Rangers that help protect the city.  We're sending you there now..."),
+    " ",
+    N_("...oh no! Now the Emperor himself is under attack, from new types of comets: these problems are multiplying! To fight these, you need great skill. We think you can do it. Join the Rangers and help save the city!"),
+    ""
+  },
+  //ace
+  {
+    "-[Esc] to skip",
+    N_("Mission Four: Imperial Ace"),
+    "--------------------------",
+    N_("You did it! The Emperor wants to thank you in person. We are taking you to his ice palace for a great honor: you will become the Imperial Ace!"),
+    " ",
+    N_("But right in the middle of the ceremony, a new attack from the land of Division starts!"),
+    N_("Now is no time for resting; the city needs your help!"),
+    ""
+  },
+  //commando
+  {
+    "-[Esc] to skip",
+    N_("Final Mission: Computing Commando"),
+    "---------------------------------",
+    N_("Penguin scientists have learned that all these attacks are coming from a secret base, and they need you to go fight the final battle. They also give you this clue: first do multiplication and division, and then do addition and subtraction."),
+    N_("I hope that hint helps!"),
+    " ",
+    N_("This is it! You can stop these attacks forever, Commando!"),
+    ""
+  },
+};
+
+int start_campaign();
+
+#endif // CAMPAIGN_H

Added: tuxmath/trunk/src/compiler.h
===================================================================
--- tuxmath/trunk/src/compiler.h	                        (rev 0)
+++ tuxmath/trunk/src/compiler.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,157 @@
+/*
+  compiler.h
+
+  Compiler-specific #defines and such
+  for Tux Paint
+
+  Mostly by Albert Cahalan <albert at users.sf.net>
+  Copyright (c) 2002-2006
+
+  http://www.newbreedsoftware.com/tuxpaint/
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+  (See COPYING.txt)
+
+  June 14, 2002 - February 18, 2006
+  $Id: compiler.h,v 1.5 2006/08/27 21:00:55 wkendrick Exp $
+
+  June 09, 2008:
+  Brought into TuxMath by Brendan Luchen as part of pixel-manipulation
+  code, with blessings of Bill Kendrick.
+
+*/
+
+#ifdef WIN32
+/* Horrible, dangerous macros. */
+/*
+  The SDL stderr redirection trick doesn't seem to work for perror().
+  This does pretty much the same thing.
+*/
+#define perror(str) ({ \
+  if ( (str) && *(str) ) \
+    fprintf(stderr,"%s : ",(str)); \
+  fprintf(stderr, \
+          "%s [%d]\n", \
+          (errno<_sys_nerr)?_sys_errlist[errno]:"unknown",errno ); \
+})
+
+/*
+  MinGW implementation of isspace() crashes on some Win98 boxes
+  if c is 'out-of-range'.
+*/
+#define isspace(c) (((c) == 0x20) || ((c) >= 0x09 && (c) <= 0x0D))
+
+/*
+  WIN32 and MINGW don't have strcasestr().
+*/
+#define NOMINMAX
+#include "shlwapi.h"
+#define strcasestr StrStrI
+#endif /* WIN32 */
+
+
+
+
+#ifdef __GNUC__
+// This version has strict type checking for safety.
+// See the "unnecessary" pointer comparison. (from Linux)
+#define min(x,y) ({ \
+  typeof(x) _x = (x);     \
+  typeof(y) _y = (y);     \
+  (void) (&_x == &_y);            \
+  _x < _y ? _x : _y; })
+#define max(x,y) ({ \
+  typeof(x) _x = (x);     \
+  typeof(y) _y = (y);     \
+  (void) (&_x == &_y);            \
+  _x > _y ? _x : _y; })
+#else
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+#define max(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+
+#define clamp(lo,value,hi)    (min(max(value,lo),hi))
+
+
+// since gcc-2.5
+#ifdef __GNUC__
+#define NORETURN __attribute__((__noreturn__))
+#define FUNCTION __attribute__((__const__))	// no access to global mem, even via ptr, and no side effect
+#else
+#define NORETURN
+#define FUNCTION
+#endif
+
+#if !defined(restrict) && __STDC_VERSION__ < 199901
+#if __GNUC__ > 2 || __GNUC_MINOR__ >= 92
+#define restrict __restrict__
+#else
+#warning No restrict keyword?
+#define restrict
+#endif
+#endif
+
+
+#if __GNUC__ > 2 || __GNUC_MINOR__ >= 96
+// won't alias anything, and aligned enough for anything
+#define MALLOC __attribute__ ((__malloc__))
+// no side effect, may read globals
+#ifndef WIN32
+#define PURE __attribute__ ((__pure__))
+#endif
+// tell gcc what to expect:   if(unlikely(err)) die(err);
+#define likely(x)       __builtin_expect(!!(x),1)
+#define unlikely(x)     __builtin_expect(!!(x),0)
+#define expected(x,y)   __builtin_expect((x),(y))
+#else
+#define MALLOC
+#define PURE
+#define likely(x)       (x)
+#define unlikely(x)     (x)
+#define expected(x,y)   (x)
+#endif
+
+
+#ifdef __powerpc__
+// Ticks at 1/4  the memory bus clock (24.907667 MHz on Albert's Mac Cube)
+// This is good for 80-second diff or 160-second total.
+#define CLOCK_ASM(tbl) asm volatile("mftb %0" : "=r" (tbl))
+#define CLOCK_TYPE unsigned long
+#ifndef CLOCK_SPEED
+// #warning Benchmark times are based on a 99.63 MHz memory bus.
+#define CLOCK_SPEED 24907667.0
+#endif
+#endif
+
+#ifdef __i386__
+#define CLOCK_ASM(tbl) asm volatile("rdtsc" : "=A" (tbl))
+#define CLOCK_TYPE unsigned long long
+#ifndef CLOCK_SPEED
+// #warning Benchmark times are based on a 450 MHz CPU.
+#define CLOCK_SPEED 450000000.0
+#endif
+#endif
+
+#ifndef CLOCK_ASM
+// #warning No idea how to read CPU cycles for you, sorry.
+#define CLOCK_ASM(tbl)
+#define CLOCK_TYPE unsigned long
+#define CLOCK_SPEED 1000000000.0
+#endif
+
+#ifdef NO_ASM
+#undef CLOCK_ASM
+#define CLOCK_ASM(x) x=42
+#endif

Added: tuxmath/trunk/src/convert_utf.c
===================================================================
--- tuxmath/trunk/src/convert_utf.c	                        (rev 0)
+++ tuxmath/trunk/src/convert_utf.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,122 @@
+/***************************************************************************
+                          convert_utf.c 
+
+                             -------------------
+    begin                :  Feb 08 2009
+    copyright            : (C) 2009 by David Bruce
+    email                : davidstuartbruce at gmail.com
+
+    This file contains simple wrapper functions for converting wchar_t and
+    UTF-8 strings using the GNU iconv library.
+
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+#include "convert_utf.h"
+#include "globals.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <iconv.h>
+
+#define UTF_BUF_LENGTH 1024
+
+/* GNU iconv()-based implementation:   */
+
+int ConvertFromUTF8(wchar_t* wide_word, const char* UTF8_word, int max_length)
+{
+  wchar_t temp_wchar[UTF_BUF_LENGTH];
+  wchar_t* wchar_start = temp_wchar;
+
+  iconv_t conv_descr;
+  size_t bytes_converted;
+  size_t in_length = (size_t)UTF_BUF_LENGTH;
+  size_t out_length = (size_t)UTF_BUF_LENGTH;
+
+  if(max_length > UTF_BUF_LENGTH)
+  {
+    fprintf(stderr, "ConvertFromUTF8() - error - requested string length %d exceeds buffer length %d\n",
+            max_length, UTF_BUF_LENGTH);
+    return 0;
+  }
+
+  tmdprintf("ConvertFromUTF8(): UTF8_word = %s\n", UTF8_word);
+
+  /* NOTE although we *should* be just able to pass "wchar_t" as the out_type, */
+  /* iconv_open() segfaults on Windows if this is done - grrr....             */
+#ifdef WIN32
+  conv_descr = iconv_open("UTF-16LE", "UTF-8");
+#else
+  conv_descr = iconv_open("wchar_t", "UTF-8");
+#endif
+
+  /* NOTE casts to prevent compiler warnings */
+  bytes_converted = iconv(conv_descr,
+                          (char**)&UTF8_word, &in_length,
+                          (char**)&wchar_start, &out_length);
+  iconv_close(conv_descr);
+  wcsncpy(wide_word, temp_wchar, max_length);
+
+  tmdprintf("ConvertToUTF8(): wide_word = %S\n", wide_word);
+
+  return wcslen(wide_word);
+}
+
+
+/* Now this uses GNU iconv and works correctly!   */
+/* This probably could be simplified - not certain */
+/* we have to copy into and out of the buffers     */
+
+/******************To be used for savekeyboard*************/
+/***Converts wchar_t string to char string*****************/
+int ConvertToUTF8(const wchar_t* wide_word, char* UTF8_word, int max_length)
+{
+  char temp_UTF8[UTF_BUF_LENGTH];
+  /* NOTE we need this because iconv_open() needs a char**.  We can't   */
+  /* just pass "&temp_UTF8" because "temp_UTF8" is really a shorthand   */
+  /* for "&temp_UTF8[0]", not its own memory location, so it doesn't    */
+  /* have its own address. We ought to be able to do this directly into */
+  /* into the argument UTF8_word string, but so far have had errors.    */
+  char* UTF8_Start = temp_UTF8;
+
+  iconv_t conv_descr;
+  size_t bytes_converted;
+  size_t in_length = (size_t)UTF_BUF_LENGTH;
+  size_t out_length = (size_t)UTF_BUF_LENGTH;
+
+  tmdprintf("ConvertToUTF8(): wide_word = %S\n", wide_word);
+
+  if(max_length > UTF_BUF_LENGTH)
+  {
+    fprintf(stderr, "ConvertToUTF8() - error - requested string length %d exceeds buffer length %d\n",
+            max_length, UTF_BUF_LENGTH);
+    return 0;
+  }
+
+  /* NOTE although we *should* be just able to pass "wchar_t" as the in_type, */
+  /* iconv_open() segfaults on Windows if this is done - grrr....             */
+#ifdef WIN32
+  conv_descr = iconv_open("UTF-8", "UTF-16LE");
+#else
+  conv_descr = iconv_open("UTF-8", "wchar_t");
+#endif
+
+  /* NOTE casts to prevent compiler warnings. While the documentation for iconv() */
+  /* says arg 2 is a "const char**", it is "char**" in the iconv.h header itself. */
+  bytes_converted = iconv(conv_descr,
+                          (char**)&wide_word, &in_length,
+                          (char**)&UTF8_Start, &out_length);
+  iconv_close(conv_descr);
+  strncpy(UTF8_word, temp_UTF8, max_length);
+
+  tmdprintf("ConvertToUTF8(): UTF8_word = %s\n", UTF8_word);
+
+  return strlen(UTF8_word);
+}

Added: tuxmath/trunk/src/convert_utf.h
===================================================================
--- tuxmath/trunk/src/convert_utf.h	                        (rev 0)
+++ tuxmath/trunk/src/convert_utf.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,27 @@
+//
+// C Interface: convert_utf
+//
+// Description: header file for simple wrapper functions to convert
+// wchar_t and utf8 strings using GNU iconv().
+//
+//
+// Author: David Bruce <davidstuartbruce at gmail.com>, (C) 2009
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef CONVERT_UTF_H
+#define CONVERT_UTF_H
+
+#include <wchar.h>
+
+/* NOTE the "max_length" parameter should generally be the size of the output     */
+/* buffer.  It must be at least one greater than the length of the return string  */
+/* so that the string can be null-terminated.                                     */
+/* Also, "max_length" must be no greater than the buffer length used internally   */
+/* in these functions (i.e. UTF_BUF_LENGTH, currently 1024)                       */
+
+int ConvertFromUTF8(wchar_t* wide_word, const char* UTF8_word, int max_length);
+int ConvertToUTF8(const wchar_t* wide_word, char* UTF8_word, int max_length);
+
+#endif
\ No newline at end of file

Added: tuxmath/trunk/src/credits.c
===================================================================
--- tuxmath/trunk/src/credits.c	                        (rev 0)
+++ tuxmath/trunk/src/credits.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,608 @@
+/*
+  credits.c
+ 
+  For TuxMath
+  Contains the text of the credits display, as well
+  as the function which displays the credits in the game window.
+
+  by Bill Kendrick
+  bill at newbreedsoftware.com
+  http://www.newbreedsoftware.com/
+
+
+  Part of "Tux4Kids" Project
+  http://www.tux4kids.org/
+  
+  August 26, 2001 - March 7, 2005
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "SDL.h"
+
+#include "tuxmath.h"
+#include "options.h"
+#include "fileops.h"
+#include "setup.h"
+#include "credits.h"
+#include "SDL_extras.h"
+
+
+char * credit_text[] = {
+  "-TUX, OF MATH COMMAND",  /* '-' at beginning makes highlighted: */
+  "COPYRIGHT 2001-2006",
+  " ",
+  "PART OF THE 'TUX4KIDS' PROJECT",
+  " ",
+  "-DESIGNED BY",
+  "SAM 'CRISWELL' HART",
+  " ",
+  "-LEAD PROGRAMMERS",
+  "BILL KENDRICK,",
+  "NEW BREED SOFTWARE",
+  "DAVID BRUCE",
+  "TIM HOLY",
+  " ",
+  "-ADDITIONAL CODE",
+  "GLEN DITCHFIELD",
+  "MICHAEL BEHRISCH",
+  "DONNY VISZNEKI",
+  "YVES COMBE",
+  "DAVID YODER",
+  "KARL OVE HUFTHAMMER",
+  "AHMED SAYED",
+  "BRENDAN LUCHEN",
+  "JESUS M. MAGER H.",
+  " ",
+  "-LEAD ARTIST",
+  "SAM HART",
+  "",
+  "-ADDITIONAL ART",
+  "BILL KENDRICK",
+  "KENDRA SWANSON & LINNEA HOLY",
+  " ",
+  "-SOUND EFFECTS",
+  "TBA",
+  " ",
+  "-MUSIC",
+  "BEYOND THE HORIZON",
+  "BY MYSTRA OF STONE ARTS, 1994",
+  " ",
+  "CCCP MAIN",
+  "BY GROO OF CNCD, 1994",
+  " ",
+  "SOFT BRILLIANCE",
+  "TJOPPBASS, 1994",
+  " ",
+  "-PACKAGERS",
+  "JESSE ANDREWS",
+  "HOLGER LEVSEN",
+  " ",
+  "-'TUX' THE PENGUIN CREATED BY",
+  "LARRY EWING",
+  " ",
+  "-TESTERS",
+  "PETE SALZMAN",
+  "ST. CATHERINE ELEM., CINCINNATI, OH",
+  "WESTWOOD ELEMENTARY, CINCINNATI, OH",
+  "LAURA BRUCE",
+  "ROOSEVELT ELEMENTARY, TAMPA, FL",
+  "KENDRA SWANSON AND LINNEA HOLY",
+  "OLD BONHOMME ELEMENTARY,",
+  "ST. LOUIS, MO",
+  "STEPHANIE CHAPIE & HOWARD NATHANSON",
+  " ",
+  " ",
+  "-WEBSITE",
+  "WWW.TUX4KIDS.COM",
+  NULL
+};
+
+
+/* Some simple pixel-based characters we can blit quickly: */
+
+char chars[39][5][5] = {
+  {".###.",
+   "#..##",
+   "#.#.#",
+   "##..#",
+   ".###."},
+
+  {"..#..",
+   ".##..",
+   "..#..",
+   "..#..",
+   ".###."},
+
+  {".###.",
+   "....#",
+   "..##.",
+   ".#...",
+   "#####"},
+
+  {".###.",
+   "....#",
+   "..##.",
+   "....#",
+   ".###."},
+
+  {"...#.",
+   "..##.",
+   ".#.#.",
+   "#####",
+   "...#."},
+
+  {"#####",
+   "#....",
+   "####.",
+   "....#",
+   "####."},
+
+  {".###.",
+   "#....",
+   "####.",
+   "#...#",
+   ".###."},
+
+  {"#####",
+   "....#",
+   "...#.",
+   "..#..",
+   ".#..."},
+
+  {".###.",
+   "#...#",
+   ".###.",
+   "#...#",
+   ".###."},
+
+  {".###.",
+   "#...#",
+   ".####",
+   "....#",
+   ".###."},
+
+  {".###.",
+   "#...#",
+   "#####",
+   "#...#",
+   "#...#"},
+
+  {"####.",
+   "#...#",
+   "####.",
+   "#...#",
+   "####."},
+  
+  {".###.",
+   "#....",
+   "#....",
+   "#....",
+   ".###."},
+  
+  {"####.",
+   "#...#",
+   "#...#",
+   "#...#",
+   "####."},
+  
+  {"#####",
+   "#....",
+   "###..",
+   "#....",
+   "#####"},
+  
+  {"#####",
+   "#....",
+   "###..",
+   "#....",
+   "#...."},
+  
+  {".###.",
+   "#....",
+   "#.###",
+   "#...#",
+   ".###."},
+  
+  {"#...#",
+   "#...#",
+   "#####",
+   "#...#",
+   "#...#"},
+  
+  {".###.",
+   "..#..",
+   "..#..",
+   "..#..",
+   ".###."},
+  
+  {"....#",
+   "....#",
+   "....#",
+   "#...#",
+   ".###."},
+  
+  {"#..#.",
+   "#.#..",
+   "##...",
+   "#.#..",
+   "#..#."},
+  
+  {"#....",
+   "#....",
+   "#....",
+   "#....",
+   "#####"},
+  
+  {"#...#",
+   "##.##",
+   "#.#.#",
+   "#...#",
+   "#...#"},
+  
+  {"#...#",
+   "##..#",
+   "#.#.#",
+   "#..##",
+   "#...#"},
+  
+  {".###.",
+   "#...#",
+   "#...#",
+   "#...#",
+   ".###."},
+  
+  {"####.",
+   "#...#",
+   "####.",
+   "#....",
+   "#...."},
+  
+  {".###.",
+   "#...#",
+   "#.#.#",
+   "#..#.",
+   ".##.#"},
+  
+  {"####.",
+   "#...#",
+   "####.",
+   "#...#",
+   "#...#"},
+  
+  {".###.",
+   "#....",
+   ".###.",
+   "....#",
+   ".###."},
+  
+  {"#####",
+   "..#..",
+   "..#..",
+   "..#..",
+   "..#.."},
+  
+  {"#...#",
+   "#...#",
+   "#...#",
+   "#...#",
+   ".###."},
+  
+  {"#...#",
+   "#...#",
+   ".#.#.",
+   ".#.#.",
+   "..#.."},
+  
+  {"#...#",
+   "#...#",
+   "#.#.#",
+   "##.##",
+   "#...#"},
+  
+  {"#...#",
+   ".#.#.",
+   "..#..",
+   ".#.#.",
+   "#...#"},
+
+  {"#...#",
+   ".#.#.",
+   "..#..",
+   "..#..",
+   "..#.."},
+  
+  {"#####",
+   "...#.",
+   "..#..",
+   ".#...",
+   "#####"},
+  
+  {".....",
+   ".....",
+   ".....",
+   "..#..",
+   ".#..."},
+
+  {".....",
+   ".....",
+   ".....",
+   "..#..",
+   "..#.."},
+  
+  {"..#..",
+   "..#..",
+   ".....",
+   ".....",
+   "....."}
+};
+
+
+//void draw_text(char * str, SDL_Rect dest);
+
+
+int line;
+
+
+int credits(void)
+{
+  int done, quit, scroll;
+  SDL_Rect subscreen, dest;
+  
+  
+  /* Clear window: */
+  
+  SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));
+  
+  
+  /* Draw title: */
+  
+  dest.x = (screen->w - images[IMG_TITLE]->w) / 2;
+  dest.y = 0;
+  dest.w = images[IMG_TITLE]->w;
+  dest.h = images[IMG_TITLE]->h;
+  
+  SDL_BlitSurface(images[IMG_TITLE], NULL, screen, &dest);
+  
+  
+  /* --- MAIN OPTIONS SCREEN LOOP: --- */
+  
+  done = 0;
+  quit = 0;
+  scroll = 0;
+  line = 0;
+
+  subscreen.x = 0;
+  subscreen.y = images[IMG_TITLE]->h;
+  subscreen.w = screen->w;
+  subscreen.h = screen->h - images[IMG_TITLE]->h;
+  quit = scroll_text(credit_text, subscreen, 2);
+  
+  /* Return the chosen command: */
+  
+  return quit;
+}
+
+int scroll_text(char* text[], SDL_Rect subscreen, int speed)
+{
+  int done = 0, quit = 0, scroll = 0, clearing = 0;
+  SDL_Event event;
+  SDL_Rect src, dest;
+  Uint32 last_time = SDL_GetTicks(), now_time;
+
+  line = 0;
+    
+  do
+    {
+      /* Handle any incoming events: */
+      while (SDL_PollEvent(&event) > 0)
+        {
+          if (event.type == SDL_QUIT)
+            {
+              /* Window close event - quit! */
+              
+              quit = 1;
+              done = 1;
+            }
+          else if (event.type == SDL_KEYDOWN)
+            {
+              if (event.key.keysym.sym == SDLK_ESCAPE)
+                {
+                  /* Escape key - quit! */
+                  done = 1;
+                }
+            }
+          else if (event.type == SDL_MOUSEBUTTONDOWN)
+            {
+              done = 1;
+            }
+        }
+
+      
+      /* Scroll: */
+
+      src = dest = subscreen;
+      src.y += speed; //amount to scroll by
+      
+      SDL_BlitSurface(screen, &src, screen, &dest);
+      
+      dest.x = subscreen.x;
+      dest.y = subscreen.y + subscreen.h - speed;
+      dest.w = subscreen.w;
+      dest.h = speed;
+
+      SDL_FillRect(screen, &dest, SDL_MapRGB(screen->format, 0, 0, 0));
+      
+      ++scroll;
+
+      if (clearing) //scroll/check, but don't display any more text
+        {
+          if (scroll > subscreen.h / speed)
+            done = 1;
+        }
+      else
+        {
+          dest.x = subscreen.x + subscreen.w / 2;
+          dest.y = subscreen.y + (subscreen.h - scroll * speed);
+          dest.w = 1;
+          dest.h = 1;
+#ifdef LINEBREAK
+	  draw_text(text[line], dest);  // translation should have already occurred
+#else
+	  if (strlen(text[line]) > 0)
+	    draw_text(gettext(text[line]), dest);
+	  else
+	    draw_text(text[line], dest);
+#endif
+
+          if (scroll * speed >= TTF_FontHeight(default_font) )
+            {
+              scroll = 0;
+              line++;
+              
+              if (text[line][0] == '\0') //end of text 
+                {
+                clearing = 1; //scroll to blank            
+                }            
+              else
+                tmdprintf("text[line]: %s\n", text[line]);
+            }
+        }
+      
+      
+      SDL_Flip(screen);
+      
+      
+      /* Pause (keep frame-rate event) */
+      
+      now_time = SDL_GetTicks();
+      if (now_time < last_time + (1000 / 20))
+        {
+          SDL_Delay(last_time + (1000 / 20) - now_time);
+        }
+      last_time = SDL_GetTicks();
+    }
+  while (!done);
+  return quit;
+}
+#if 0 //really cool effect, but not translatable. I'll leave it in in case we 
+      //decide to use it e.g. only for English
+void draw_text(char * str, int offset)
+{
+  int i, c, x, y, cur_x, start, hilite;
+  SDL_Rect dest;
+  Uint8 r, g, b;
+
+
+  if (str[0] == '-')
+  {
+    start = 1;
+    hilite = 1;
+  }
+  else
+  {
+    start = 0;
+    hilite = 0;
+  }
+  
+  
+  cur_x = (screen->w - ((strlen(str) - start) * 18)) / 2;
+  
+  for (i = start; i < strlen(str); i++)
+    { 
+      c = -1;
+      
+      if (str[i] >= '0' && str[i] <= '9')
+        c = str[i] - '0';
+      else if (str[i] >= 'A' && str[i] <= 'Z')
+        c = str[i] - 'A' + 10;
+      else if (str[i] == ',')
+        c = 36;
+      else if (str[i] == '.')
+        c = 37;
+      else if (str[i] == '\'')
+        c = 38;
+      
+      
+      if (c != -1)
+        {
+          for (y = 0; y < 5; y++)
+            {
+              if (hilite == 0)
+              {
+                r = 255 - ((line * y) % 256);
+                g = 255 / (y + 2);
+                b = (line * line * 2) % 256;
+              }
+              else
+              {
+                r = 128;
+                g = 192;
+                b = 255 - (y * 40);
+              }
+              
+              for (x = 0; x < 5; x++)
+                {
+                  if (chars[c][y][x] == '#')
+                    {
+                      dest.x = cur_x + (x * 3);
+                      dest.y = ((screen->h - (5 * 3)) + (y * 3) +
+                                (18 - offset * 2));
+                      dest.w = 3;
+                      dest.h = 3;
+                      
+                      SDL_FillRect(screen, &dest,
+                                   SDL_MapRGB(screen->format, r, g, b));
+                    }
+                }
+            }
+        }
+      
+      
+      /* Move virtual cursor: */
+      
+      cur_x = cur_x + 18;
+    }
+}
+
+#else
+
+//FIXME it's possible that generating the surface every frame taxes 
+//slower machines. If so consider returning the surface to be used 
+//as long as it's needed.
+void draw_text(char* str, SDL_Rect dest)
+{
+  SDL_Color col;
+  SDL_Surface* surf = NULL;
+  if (!str || *str == '\0')
+    return;
+
+  tmdprintf("Entering draw_text(%s)\n", str);
+  
+  if (str[0] == '-') //highlight text
+  {
+    str++;
+    col.r = 128;
+    col.g = 192;
+    col.b = 255 - (40);
+  }
+  else //normal color
+  {
+    col.r = 255 - (line % 256);
+    col.g = 255 / 2;
+    col.b = (line * line * 2) % 256;  
+  }
+
+  /* This func from SDL_extras draws with SDL_Pango if avail, */
+  /* with SDL_ttf as fallback:                                */
+  surf =  SimpleText(str, default_font, &col);
+
+  dest.x -= surf->w / 2; //center text
+  SDL_BlitSurface(surf, NULL, screen, &dest);
+  SDL_FreeSurface(surf);
+  tmdprintf("done\n");
+}
+#endif

Added: tuxmath/trunk/src/credits.h
===================================================================
--- tuxmath/trunk/src/credits.h	                        (rev 0)
+++ tuxmath/trunk/src/credits.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,26 @@
+/*
+  credits.h
+
+  For TuxMath
+  Contains the text of the credits display, as well
+  as the function which displays the credits in the game window.
+
+  by Bill Kendrick
+  bill at newbreedsoftware.com
+  http://www.newbreedsoftware.com/
+
+
+  Part of "Tux4Kids" Project
+  http://www.tux4kids.org/
+      
+  August 26, 2001 - August 28, 2001
+*/
+
+
+#ifndef CREDITS_H
+#define CREDITS_H
+
+int credits(void);
+int scroll_text(char* text[], SDL_Rect subscreen, int speed);
+void draw_text(char* str, SDL_Rect dest);
+#endif

Added: tuxmath/trunk/src/exercise_mathcards
===================================================================
(Binary files differ)


Property changes on: tuxmath/trunk/src/exercise_mathcards
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:mime-type
   + application/octet-stream

Added: tuxmath/trunk/src/exercise_mathcards.c
===================================================================
--- tuxmath/trunk/src/exercise_mathcards.c	                        (rev 0)
+++ tuxmath/trunk/src/exercise_mathcards.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,54 @@
+/*
+*  C Implementation: exercise_mathcards
+*
+* Description: 
+*
+*
+* Author: David Bruce <dbruce at tampabay.rr.com>, (C) 2007
+*
+* Copyright: See COPYING file that comes with this distribution
+*
+*/
+
+#include <stdio.h>
+#include "mathcards.h"
+
+int main()
+{
+  int i, iter, op;
+  MC_FlashCard c;
+
+  MC_Initialize();
+
+  for (i = 0; i < 100; i++)
+  {
+
+    fprintf(stderr, "\n\nGame: i = %d\n", i); 
+    op = rand() % 2;
+    MC_SetAddAllowed(op);
+    op = rand() % 2;
+    MC_SetSubAllowed(op);
+    op = rand() % 2;
+    MC_SetMultAllowed(op);
+    op = rand() % 2;
+    MC_SetDivAllowed(op);
+
+    if (!MC_StartGame())
+      continue;
+
+    iter = 0;
+
+    while(!MC_MissionAccomplished())
+    {
+      MC_NextQuestion(&c);
+      op = rand() % 2;
+      if (op)
+        MC_AnsweredCorrectly(&c);
+      else
+        MC_NotAnsweredCorrectly(&c);
+      iter++;
+    }
+//    MC_EndGame();
+  }
+  return 1;
+}

Added: tuxmath/trunk/src/factoroids.c
===================================================================
--- tuxmath/trunk/src/factoroids.c	                        (rev 0)
+++ tuxmath/trunk/src/factoroids.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,2221 @@
+/************************************************************
+ *  factoroids.c                                             *
+ *                                                          *
+ *  Description:  Code for the factor and fraction activity *
+ *                                                          *
+ *  Author:       Jesus M. Mager H. (fongog at gmail.com) 2008 *
+ *  Copyright:    GPL v3 or later                           *
+ *  							    *
+ *  Also significantly enhanced by Tim Holy - 2008          *
+ *                                                          *
+ *  Code based on the work made by:                         *
+ *               Bill Kendrick (vectoroids 1.1.0)           *
+ *               and Bill Kendrick, David Bruce, Tim Holy   *
+ *               and others (Tuxmath 1.6.3)                 *
+ *                                                          *
+ *  TuxMath                                                 *
+ *  Part of "Tux4Kids" Project                              *
+ *  http://tux4kids.alioth.debian.org/                      *
+ ************************************************************/
+
+#include "tuxmath.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "SDL.h"
+#ifndef NOSOUND
+#include "SDL_mixer.h"
+#endif
+#include "SDL_image.h"
+#include "SDL_rotozoom.h"
+#include "SDL_extras.h"
+
+#include "game.h"
+#include "fileops.h"
+#include "setup.h"
+#include "mathcards.h"
+#include "loaders.h"
+#include "titlescreen.h"
+#include "options.h"
+
+#define FPS 15                     /* 15 frames per second */
+#define MS_PER_FRAME (1000 / FPS)
+#define BASE_RES_X 1280 
+
+#define MAX_LASER 5
+#define MAX_ASTEROIDS 50
+#define NUM_TUXSHIPS 2
+#define NUM_SPRITES 11
+#define TUXSHIP_LIVES 3
+#define DEG_PER_ROTATION 2
+#define NUM_OF_ROTO_IMGS 360/DEG_PER_ROTATION
+/* TUXSHIP_DECEL controls "friction" - 1 means ship glides infinitely, 0 stops it instantly */
+#define TUXSHIP_DECEL 0.8
+#define DEG_TO_RAD 0.0174532925
+#define MAX(a,b)           (((a) > (b)) ? (a) : (b))
+
+/********* Enumerations ***********/
+
+enum{
+  FACTOROIDS_GAME,
+  FRACTIONS_GAME
+};
+
+/********* Structures *********/
+
+typedef struct colorRGBA_type {
+  Uint8 r;
+  Uint8 g;
+  Uint8 b;
+  Uint8 a;
+} ColorRGBA_type;
+
+typedef struct asteroid_type {
+  int alive, size;
+  int angle, angle_speed;
+  int xspeed, yspeed;
+  int x, y;
+  int rx, ry;
+  int centerx, centery;
+  int radius;
+  int fact_number;
+  int isprime;
+  int a, b; /*  a / b */
+  int count;
+} asteroid_type;
+
+
+typedef struct tuxship_type {
+  int lives, size;
+  int xspeed, yspeed;
+  int x, y;
+  int rx, ry;
+  int x1,y1,x2,y2,x3,y3;
+  int radius;
+  int centerx, centery;
+  int angle;
+  int hurt, hurt_count;
+  int count;
+} tuxship_type;
+
+
+typedef struct FF_laser_type{
+  int alive;
+  int x, y;
+  int destx,desty;
+  int r, g, b;
+  int count;
+  int angle;
+  int m;
+} FF_laser_type;
+
+
+typedef struct {
+  int x_is_blinking;
+  int extra_life_is_blinking;
+  int laser_enabled;
+} help_controls_type;
+
+/********* Global vars ************/
+
+/* Trig junk:  (thanks to Atari BASIC for this) */
+
+static int trig[12] = {
+  1024,
+  1014,
+  984,
+  935,
+  868,
+  784,
+  685,
+  572,
+  448,
+  316,
+  117,
+  0
+};
+
+// ControlKeys
+static int left_pressed;
+static int right_pressed;
+static int up_pressed;
+static int shift_pressed;
+static int shoot_pressed;
+
+// GameControl
+static int game_status;
+//static int gameover_counter;
+static int escape_received;
+
+//SDL_Surfaces:
+static SDL_Surface* IMG_tuxship[NUM_OF_ROTO_IMGS];
+static SDL_Surface* IMG_asteroids1[NUM_OF_ROTO_IMGS];
+static SDL_Surface* IMG_asteroids2[NUM_OF_ROTO_IMGS];
+static SDL_Surface* bkgd = NULL; //640x480 background (windowed)
+static SDL_Surface* scaled_bkgd = NULL; //native resolution (fullscreen)
+
+
+// Game type
+static int FF_game;
+
+// Game vars
+static int score;
+static int wave;
+static int paused;
+static int escape_received;
+static int game_status;
+static int SDL_quit_received;
+static int quit;
+static int digits[3];
+static int num;
+
+static int neg_answer_picked;
+static int tux_pressing;
+static int doing_answer;
+static int level_start_wait;
+static int tux_img;
+//static int FF_level;
+
+static asteroid_type* asteroid = NULL;
+static tuxship_type tuxship;
+static FF_laser_type laser[MAX_LASER];
+
+static int NUM_ASTEROIDS;
+static int counter;
+static int xdead, ydead, isdead, countdead;
+static int roto_speed;
+
+/*************** The Factor and Faraction Activiy game Functions ***************/
+
+/* Local function prototypes: */
+
+static int FF_init(void);
+static void FF_intro(void);
+
+static void FF_handle_ship(void);
+static void FF_handle_asteroids(void);
+static void FF_handle_answer(void);
+static int check_exit_conditions(void);
+static void FF_draw(void);
+static void FF_draw_bkgr(void);
+static void FF_draw_led_console(void);
+static void draw_console_image(int i);
+
+static SDL_Surface* current_bkgd()
+  { return screen->flags & SDL_FULLSCREEN ? scaled_bkgd : bkgd; }
+
+static void FF_add_level(void);
+static int FF_over(int game_status);
+static void FF_exit_free(void);
+
+static int FF_add_laser(void);
+static int FF_add_asteroid(int x, int y, int xspeed, int yspeed, int size, int angle, int 				   angle_speed, int fact_num, int a, int b, int new_wave);
+static int FF_destroy_asteroid(int i, float xspeed, float yspeed);
+
+static void FF_ShowMessage(char* str1, char* str2, char* str3, char* str4);
+
+static SDL_Surface* get_asteroid_image(int size,int angle);
+static int AsteroidColl(int astW,int astH,int astX,int astY,
+                 int x, int y);
+static int is_prime(int num);
+static int fast_cos(int angle);
+static int fast_sin(int angle);
+static void game_handle_user_events(void);
+static int game_mouse_event(SDL_Event event);
+
+/************** factors(): The factor main function ********************/
+void factors(void)
+{
+  Uint32 last_time, now_time; 
+  
+  quit = 0;
+  counter = 0;
+  tux_img = IMG_TUX_CONSOLE1;
+
+  #ifdef TUXMATH_DEBUG
+     fprintf(stderr, "Entering factors():\n");
+  #endif
+
+  FF_game = FACTOROIDS_GAME;
+  
+  if (!FF_init())
+  {
+    fprintf(stderr, "FF_init() failed!\n");
+    FF_exit_free();
+    return;
+  } 
+
+  while (game_status == GAME_IN_PROGRESS)
+  {
+    last_time = SDL_GetTicks();
+    counter++; 
+    
+    if(counter%15 == 0)
+    {
+      if(tux_img<IMG_TUX_CONSOLE4)
+        tux_img++;
+      else 
+        tux_img=IMG_TUX_CONSOLE1;
+    }
+
+    game_handle_user_events();
+
+    FF_handle_ship();
+    FF_handle_asteroids();
+    FF_handle_answer();
+    FF_draw();
+    SDL_Flip(screen);
+
+    game_status = check_exit_conditions();
+
+    if (paused)
+    {
+      pause_game();
+      paused = 0;
+    }
+
+
+#ifndef NOSOUND
+    if (Opts_UsingSound())
+    {
+      //...when the music's over, turn out the lights!
+      //...oops, wrong song! Actually, we just pick next music at random:
+      if (!Mix_PlayingMusic())
+      {
+        Mix_PlayMusic(musics[MUS_GAME + (rand() % 3)], 0);
+      }
+    }
+#endif
+
+      /* Pause (keep frame-rate event) */
+    now_time = SDL_GetTicks();
+    if (now_time < last_time + MS_PER_FRAME)
+    {
+      //Prevent any possibility of a time wrap-around
+      // (this is a very unlikely problem unless there is an SDL bug
+      //  or you leave tuxmath running for 49 days...)
+      now_time = (last_time+MS_PER_FRAME) - now_time;  // this holds the delay
+      if (now_time > MS_PER_FRAME)
+	now_time = MS_PER_FRAME;
+      SDL_Delay(now_time);
+    }
+  }
+  FF_over(game_status);
+}
+
+
+/************** fractions(): The fractions main function ********************/
+void fractions(void)
+{
+
+  Uint32 last_time, now_time; 
+  
+  quit = 0;
+  counter = 0;
+  tux_img = IMG_TUX_CONSOLE1;
+
+  
+  #ifdef TUXMATH_DEBUG
+     fprintf(stderr, "Entering factors():\n");
+  #endif
+  
+  /*****Initalizing the Factor activiy *****/
+  FF_game = FRACTIONS_GAME;
+
+  if (!FF_init())
+  {
+    fprintf(stderr, "FF_init() failed!\n");
+    FF_exit_free();
+    return;
+  } 
+
+  /************ Main Loop **************/
+  while (game_status == GAME_IN_PROGRESS)
+  {
+    last_time = SDL_GetTicks();
+    counter++;
+      
+    if(counter%15 == 0)
+    {    
+      if(tux_img < IMG_TUX_CONSOLE4)
+        tux_img++;
+      else 
+        tux_img = IMG_TUX_CONSOLE1;
+    }
+
+    game_handle_user_events();
+
+    FF_handle_ship();
+    FF_handle_asteroids();
+    FF_handle_answer();
+    FF_draw();
+    SDL_Flip(screen);
+
+    game_status = check_exit_conditions();
+
+    if (paused)
+    {
+      pause_game();
+      paused = 0;
+    }
+
+
+#ifndef NOSOUND
+    if (Opts_UsingSound())
+    {
+      if (!Mix_PlayingMusic())
+      {
+        Mix_PlayMusic(musics[MUS_GAME + (rand() % 3)], 0);
+      }  
+    }
+#endif
+
+    /* Pause (keep frame-rate event) */
+    now_time = SDL_GetTicks();
+    if (now_time < last_time + MS_PER_FRAME)
+    {
+        //Prevent any possibility of a time wrap-around
+        // (this is a very unlikely problem unless there is an SDL bug
+        //  or you leave tuxmath running for 49 days...)
+      now_time = (last_time + MS_PER_FRAME) - now_time;  // this holds the delay
+      if (now_time > MS_PER_FRAME)
+        now_time = MS_PER_FRAME;
+      SDL_Delay(now_time);
+    }
+  }
+  FF_over(game_status);
+}
+
+
+/************ Initialize all vars... ****************/
+static int FF_init(void)
+{
+  int i;
+  float zoom;
+
+  SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));
+  SDL_Flip(screen);
+  
+  FF_intro();
+  
+  if(screen->h < 600 && screen->w < 800)
+    zoom = 0.65;
+  else
+    zoom=(float)screen->w/(float)BASE_RES_X;
+
+  printf("The zoome rate is: %f\n", zoom);
+
+  /*************** Precalculating software rotation ***************/
+
+  for(i = 0; i < NUM_OF_ROTO_IMGS; i++)
+  {
+    //rotozoomSurface (SDL_Surface *src, double angle, double zoom, int smooth);
+    IMG_tuxship[i] = rotozoomSurface(images[IMG_SHIP01], i * DEG_PER_ROTATION, zoom, 1);
+
+    if (IMG_tuxship[i] == NULL)
+    {
+      fprintf(stderr,
+              "\nError: rotozoomSurface() of images[IMG_SHIP01] for i = %d returned NULL\n", i);
+      return 0;
+    }
+
+    IMG_asteroids1[i] = rotozoomSurface(images[IMG_ASTEROID1], i * DEG_PER_ROTATION, zoom, 1);
+
+    if (IMG_asteroids1[i] == NULL)
+    {
+      fprintf(stderr,
+              "\nError: rotozoomSurface() of images[IMG_ASTEROID1] for i = %d returned NULL\n", i);
+      return 0;
+    }
+
+    IMG_asteroids2[i] = rotozoomSurface(images[IMG_ASTEROID2], i*DEG_PER_ROTATION, zoom, 1);
+
+    if (IMG_asteroids2[i] == NULL)
+    {
+      fprintf(stderr,
+              "\nError: rotozoomSurface() of images[IMG_ASTEROID2] for i = %d returned NULL\n", i);
+      return 0;
+    }
+  }
+
+
+  /********   Set up properly scaled and optimized background surfaces: *********/
+  /* NOTE - optimization code moved into LoadBothBkgds() so rest of program     */
+  /* can take advantage of it - DSB                                             */
+
+  LoadBothBkgds("factoroids/gbstars.png", &scaled_bkgd, &bkgd);
+
+  if (bkgd == NULL || scaled_bkgd == NULL)
+  {
+    fprintf(stderr,
+       "\nError: could not scale background\n");
+    return 0;
+  }
+
+
+  // Allocate memory 
+  asteroid = NULL;  // set in case allocation fails partway through
+  asteroid = (asteroid_type *) malloc(MAX_ASTEROIDS * sizeof(asteroid_type));
+  
+  if (asteroid == NULL)
+  {
+    printf("Allocation of asteroids failed");
+    return 0;
+  }
+
+  NUM_ASTEROIDS = 4;
+
+  /**************Setting up the ship values! **************/
+  tuxship.x = ((screen->w)/2) - 20;
+  tuxship.y = ((screen->h)/2) - 20;
+  tuxship.lives = TUXSHIP_LIVES;
+  tuxship.hurt = 0;
+  tuxship.hurt_count = 0;
+  tuxship.angle = 90;
+  tuxship.xspeed = 0;
+  tuxship.yspeed = 0;
+  tuxship.radius = (images[IMG_SHIP01]->h)/2;
+
+  tuxship.x1 = images[IMG_SHIP01]->w-(images[IMG_SHIP01]->w/8);
+  tuxship.y1 = images[IMG_SHIP01]->h/2;
+  tuxship.x2 = images[IMG_SHIP01]->w/8;
+  tuxship.y2 = images[IMG_SHIP01]->h/8;
+  tuxship.x3 = images[IMG_SHIP01]->w/8;
+  tuxship.y3 = images[IMG_SHIP01]->h-(images[IMG_SHIP01]->h/8);
+
+  /*  --- reset all controls:  ---  */
+  left_pressed = 0;
+  right_pressed = 0;
+  up_pressed = 0;
+  shift_pressed = 0;
+  shoot_pressed = 0;
+
+  score = 1;
+  wave = 0;
+  xdead = 0;
+  ydead = 0;
+  isdead = 0;
+  countdead = 0;
+  escape_received = 0;
+  game_status = GAME_IN_PROGRESS;
+
+  FF_add_level();
+
+  for (i = 0; i < MAX_LASER; i++)
+    laser[i].alive = 0;
+
+  // Wait for click or keypress to start (get out if user presses Esc) :
+  while(1)
+  {
+    SDL_PollEvent(&event);
+    if (event.type == SDL_QUIT)
+    {
+      SDL_quit_received = 1;
+      quit = 1;
+      return 1;
+    }
+    else if (event.type == SDL_MOUSEBUTTONDOWN)
+    {
+      return 1;
+    }
+    else if (event.type == SDL_KEYDOWN)
+    {
+      if (event.key.keysym.sym == SDLK_ESCAPE)
+        escape_received = 1;
+      return 1;
+    }
+  }
+}
+
+
+static void FF_intro(void)
+{
+  static SDL_Surface* IMG_factors;
+  static SDL_Surface* IMG_fractions;
+
+//  SDL_Event event;
+  SDL_Rect rect;
+
+  float zoom;
+
+  if(screen->h < 600 && screen->w < 800)
+    zoom = 0.65;
+  else
+    zoom=(float)screen->w/(float)BASE_RES_X;
+  
+  IMG_factors   = rotozoomSurface(images[IMG_FACTOROIDS], 0, zoom, 1);
+  IMG_fractions = rotozoomSurface(images[IMG_FACTORS], 0, zoom, 1);
+
+  FF_draw_bkgr();
+  if(FF_game == FACTOROIDS_GAME)
+  {
+
+    rect.x = (screen->w/2) - (IMG_factors->w/2);
+    rect.y = (screen->h)/7;
+    SDL_BlitSurface(IMG_factors, NULL, screen, &rect);
+    FF_ShowMessage(_("FACTOROIDS: to win, you need destroy all the asteroids."),
+		   _("Use the arrow keys to turn or go forward.  Aim at an asteroid,"),
+		   _("type one of its factors, and press space or return"),
+		   _("to split it into its factors.  Rocks with prime numbers are destroyed!"));
+    SDL_BlitSurface(IMG_asteroids1[3],NULL,screen,&rect);
+  }
+  else if (FF_game == FRACTIONS_GAME)
+  {
+    rect.x = (screen->w/2)-(IMG_fractions->w/2);
+    rect.y = (screen->h)/7;
+    SDL_BlitSurface(IMG_fractions,NULL,screen,&rect);
+    FF_ShowMessage(_("FRACTIONS: to win, you need destroy all the asteroids"),
+		   _("Use the arrow keys to turn or go forward.  Aim at an asteroid,"),
+		   _("type a number that can simplify the fraction, and press space or return"),
+		   _("to split it.  Destroy fractions that can not be further simplified in a single shot!"));
+  }
+
+  SDL_FreeSurface(IMG_factors);
+  SDL_FreeSurface(IMG_fractions);
+}
+
+static void FF_handle_ship(void)
+{
+//FIXME - am I missing something -- doesn't this just reduce to 
+//"tuxship.centerx = tuxship.x" and likewise for y???
+/****************** Ship center... ******************/
+
+  tuxship.centerx = ((IMG_tuxship[tuxship.angle/DEG_PER_ROTATION]->w)/2) + 
+        (tuxship.x - (IMG_tuxship[tuxship.angle/DEG_PER_ROTATION]->w/2));
+  tuxship.centery = ((IMG_tuxship[tuxship.angle/DEG_PER_ROTATION]->h)/2) + 
+        (tuxship.y - (IMG_tuxship[tuxship.angle/DEG_PER_ROTATION]->h/2));  
+
+/******************* Ship live *********************/
+  
+  if(tuxship.hurt)
+  {
+    tuxship.hurt_count--;
+    if(tuxship.hurt_count <= 0)
+	tuxship.hurt = 0;
+  }
+/****************** Rotate Ship *********************/
+
+  if(right_pressed || left_pressed)
+  {
+    if(roto_speed < 10)
+    {
+      roto_speed = roto_speed + 2;
+    }
+  }
+  else
+  {
+    roto_speed = 1;
+  }
+
+  if (right_pressed)
+  {
+    tuxship.angle = tuxship.angle - DEG_PER_ROTATION * roto_speed;
+    if (tuxship.angle < 0)
+      tuxship.angle = tuxship.angle + 360;
+
+    tuxship.x1= fast_cos(DEG_PER_ROTATION*-roto_speed) * tuxship.centerx
+               -fast_sin(DEG_PER_ROTATION*-roto_speed) * tuxship.centery;
+    tuxship.y1= fast_sin(DEG_PER_ROTATION*-roto_speed) * tuxship.centerx
+               +fast_cos(DEG_PER_ROTATION*-roto_speed) * tuxship.centery;
+
+  }
+  else if (left_pressed)
+  {
+    tuxship.angle=tuxship.angle + DEG_PER_ROTATION * roto_speed;
+    if (tuxship.angle >= 360)
+      tuxship.angle = tuxship.angle - 360;
+
+    tuxship.x1= fast_cos(DEG_PER_ROTATION*roto_speed) * tuxship.centerx
+               -fast_sin(DEG_PER_ROTATION*roto_speed) * tuxship.centery;
+    tuxship.y1= fast_sin(DEG_PER_ROTATION*roto_speed * tuxship.centerx
+               +fast_cos(DEG_PER_ROTATION*roto_speed)) * tuxship.centery;
+
+  }
+
+/**************** Move, and increse speed ***************/
+
+      
+  if (up_pressed && (tuxship.lives > 0))
+  {
+     tuxship.xspeed = tuxship.xspeed + ((fast_cos(tuxship.angle >> 3) * 3) >> 10);
+     tuxship.yspeed = tuxship.yspeed - ((fast_sin(tuxship.angle >> 3) * 3) >> 10);
+  }
+  else
+  {
+    if ((counter % 2) == 0)
+    {
+       tuxship.xspeed = tuxship.xspeed * TUXSHIP_DECEL; 
+       tuxship.yspeed = tuxship.yspeed * TUXSHIP_DECEL;
+    }
+  }
+
+  tuxship.x = tuxship.x + tuxship.xspeed;
+  tuxship.y = tuxship.y + tuxship.yspeed;
+
+/*************** Wrap ship around edges of screen ****************/
+  
+  if(tuxship.x >= (screen->w))
+    tuxship.x = tuxship.x - (screen->w);
+  else if (tuxship.x < -60)
+    tuxship.x = tuxship.x + (screen->w);
+      
+  if(tuxship.y >= (screen->h))
+    tuxship.y = tuxship.y - (screen->h);
+  else if (tuxship.y < -60)
+	tuxship.y = tuxship.y + (screen->h);
+
+/**************** Shoot ***************/   
+  if(shoot_pressed)
+  {
+    FF_add_laser();
+    shoot_pressed=0;
+  }
+}
+
+
+static void FF_handle_asteroids(void){
+
+  SDL_Surface* surf;
+  int i, found=0;
+      for (i = 0; i < MAX_ASTEROIDS; i++){
+	  if (asteroid[i].alive)
+	    {
+
+	      found=1;
+
+	      /*************** Rotate asteroid ****************/ 
+	      
+	      asteroid[i].angle = (asteroid[i].angle + asteroid[i].angle_speed);
+
+	      // Wrap rotation angle... 
+	      
+	      if (asteroid[i].angle < 0)
+		asteroid[i].angle = asteroid[i].angle + 360;
+	      else if (asteroid[i].angle >= 360)
+		asteroid[i].angle = asteroid[i].angle - 360;
+
+             /**************Move the astroids ****************/ 
+	      surf=get_asteroid_image(asteroid[i].size,asteroid[i].angle);
+
+	      asteroid[i].rx = asteroid[i].rx + asteroid[i].xspeed;
+	      asteroid[i].ry = asteroid[i].ry + asteroid[i].yspeed;
+
+	      asteroid[i].x  = (asteroid[i].rx - (surf->w/2));
+	      asteroid[i].y  = (asteroid[i].ry - (surf->h/2));
+ 	      
+	      // Wrap asteroid around edges of screen: 
+	      
+	      if (asteroid[i].x >= (screen->w))
+		asteroid[i].rx = asteroid[i].rx - (screen->w);
+	      else if (asteroid[i].x < 0)
+		asteroid[i].rx = asteroid[i].rx + (screen->w);
+	      
+	      if (asteroid[i].y >= (screen->h))
+		asteroid[i].ry = asteroid[i].ry - (screen->h);
+	      else if (asteroid[i].ry < 0)
+		asteroid[i].ry = asteroid[i].ry + (screen->h);
+	      /**************Center Asteroids**************/
+
+  	      asteroid[i].centerx=((surf->w)/2)+(asteroid[i].x-5);
+  	      asteroid[i].centery=((surf->h)/2)+(asteroid[i].y-5);
+
+              /*************** Collisions! ****************/
+
+              if(AsteroidColl(surf->w, surf->h, asteroid[i].x, asteroid[i].y, tuxship.centerx, tuxship.centery))
+	      {
+		if(!tuxship.hurt)
+		{
+		  xdead=asteroid[i].centerx;
+		  ydead=asteroid[i].centery;
+		     
+		  tuxship.lives--;
+		  tuxship.hurt=1;
+		  tuxship.hurt_count=50;
+		  FF_destroy_asteroid(i, tuxship.xspeed, tuxship.yspeed);
+		  playsound(SND_EXPLOSION);
+			 
+		}
+	      }
+        }
+     }
+  if(!found)
+    FF_add_level();
+}
+
+static void FF_handle_answer(void)
+{
+  
+  num = (digits[0] * 100 +
+         digits[1] * 10 +
+         digits[2]);
+  /* negative answer support DSB */
+  if (neg_answer_picked)
+  {
+    num = -num;
+  }	
+
+  if (!doing_answer)
+  {
+    return;
+  }
+  
+  doing_answer = 0;
+  
+    /* Clear digits: */
+  digits[0] = 0;
+  digits[1] = 0;
+  digits[2] = 0;
+  neg_answer_picked = 0;
+
+}
+
+static SDL_Surface* get_asteroid_image(int size,int angle)
+{
+  if (size == 0)
+    return IMG_asteroids1[angle/DEG_PER_ROTATION];
+  else
+    return IMG_asteroids2[angle/DEG_PER_ROTATION];
+}
+
+static void FF_draw(void){
+
+  int i, offset;
+  int xnum, ynum;
+  char str[64];
+  SDL_Surface* surf;
+  SDL_Rect dest;
+  
+  SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));
+
+  /************ Draw Background ***************/ 
+
+  FF_draw_bkgr();
+
+/******************* Draw laser *************************/
+  for (i=0;i<MAX_LASER;i++){
+    if(laser[i].alive)
+    {
+      if(laser[i].count>0)
+      {
+        laser[i].count--;
+	laser[i].x=laser[i].x+tuxship.xspeed;
+   	laser[i].y=laser[i].y+tuxship.yspeed;
+	laser[i].destx=laser[i].destx+tuxship.xspeed;
+	laser[i].desty=laser[i].desty+tuxship.yspeed;
+        draw_line(laser[i].x, laser[i].y, laser[i].destx, laser[i].desty,
+		  laser[i].count*18, 0, 0);
+      } else if (laser[i].count <= 0)
+      {
+        laser[i].alive=0;
+      }
+    }
+  }
+  /*************** Draw Ship ******************/ 
+
+  if(!tuxship.hurt || (tuxship.hurt && tuxship.hurt_count%2==0)){
+     dest.x = (tuxship.x - (IMG_tuxship[tuxship.angle/DEG_PER_ROTATION]->w/2));
+     dest.y = (tuxship.y - (IMG_tuxship[tuxship.angle/DEG_PER_ROTATION]->h/2));
+     dest.w = IMG_tuxship[tuxship.angle/DEG_PER_ROTATION]->w;
+     dest.h = IMG_tuxship[tuxship.angle/DEG_PER_ROTATION]->h;
+	
+     SDL_BlitSurface(IMG_tuxship[tuxship.angle/DEG_PER_ROTATION], NULL, screen, &dest);
+  }
+  /************* Draw Asteroids ***************/
+  for(i=0; i<MAX_ASTEROIDS; i++){
+    if(asteroid[i].alive>0){
+     
+     xnum=0;
+     ynum=0;
+
+     dest.x = asteroid[i].x;
+     dest.y = asteroid[i].y; 
+
+     surf=get_asteroid_image(asteroid[i].size,asteroid[i].angle);
+
+     dest.w = surf->w;
+     dest.h = surf->h;
+
+     SDL_BlitSurface(surf, NULL, screen, &dest);
+
+     // Wrap the numbers of the asteroids
+     if((asteroid[i].centery)>23 && (asteroid[i].centery)<screen->h)
+     {
+       if((asteroid[i].centerx)>0 && (asteroid[i].centerx)<screen->w)
+       {
+         xnum=asteroid[i].centerx-3;
+         ynum=asteroid[i].centery;
+       }
+       else if((asteroid[i].centerx)<=0){
+         xnum=20;
+         ynum=asteroid[i].centery;
+       }
+       else if((asteroid[i].centerx)<=screen->w){
+         xnum=screen->w-20;
+         ynum=asteroid[i].centery;
+       }
+     }
+     else if((asteroid[i].centery)<=23)
+     {
+       xnum=asteroid[i].centerx;
+       ynum=23;
+     }
+     else if((asteroid[i].centery)>=screen->h)
+     {
+       xnum=asteroid[i].centerx;
+       ynum=screen->h-7;
+     }
+
+     //Draw Numbers
+     if(FF_game==FACTOROIDS_GAME)
+     {   
+       sprintf(str, "%.1d", asteroid[i].fact_number);
+       draw_nums(str, xnum, ynum);
+     }
+     else if (FF_game==FRACTIONS_GAME)
+     {
+       sprintf(str, "%d", asteroid[i].a);
+       draw_nums(str, xnum, ynum); 
+       draw_line(xnum, ynum + 4, xnum + 30, ynum + 4,
+		 255, 255, 255);
+       sprintf(str, "%d", asteroid[i].b);
+       draw_nums(str, xnum, ynum + 35);
+     }
+    }
+  }
+  /*************** Draw Steam ***************/
+  
+  if(isdead)
+  {
+    dest.x = xdead;
+    dest.y = ydead;
+    SDL_BlitSurface(images[IMG_STEAM1+countdead], NULL, screen, &dest);
+    countdead++;
+    if(countdead > 5)
+    {
+      isdead = 0;
+      countdead = 0;
+    }
+  }
+
+  /* Draw wave: */
+  if (1)//Opts_BonusCometInterval())
+    offset = images[IMG_EXTRA_LIFE]->w + 5;
+  else
+    offset = 0;
+
+  dest.x = offset;
+
+  dest.y = 0;
+  dest.w = images[IMG_WAVE]->w;
+  dest.h = images[IMG_WAVE]->h;
+
+  SDL_BlitSurface(images[IMG_WAVE], NULL, screen, &dest);
+
+  sprintf(str, "%d", wave);
+  draw_numbers(str, offset+images[IMG_WAVE]->w + (images[IMG_NUMBERS]->w / 10), 0);
+
+  /* Draw "score" label: */
+  dest.x = (screen->w - ((images[IMG_NUMBERS]->w/10) * 7) -
+	        images[IMG_SCORE]->w -
+                images[IMG_STOP]->w - 5);
+  dest.y = 0;
+  dest.w = images[IMG_SCORE]->w;
+  dest.h = images[IMG_SCORE]->h;
+
+  SDL_BlitSurface(images[IMG_SCORE], NULL, screen, &dest);
+        
+  sprintf(str, "%.6d", score);
+  draw_numbers(str,
+               screen->w - ((images[IMG_NUMBERS]->w / 10) * 6) - images[IMG_STOP]->w - 5,
+               0);
+
+  /* Draw stop button: */
+//  if (!help_controls.x_is_blinking || (frame % 10 < 5)) {
+  dest.x = (screen->w - images[IMG_STOP]->w);
+  dest.y = 0;
+  dest.w = images[IMG_STOP]->w;
+  dest.h = images[IMG_STOP]->h;
+    
+  SDL_BlitSurface(images[IMG_STOP], NULL, screen, &dest);
+ // }
+
+  /************* Draw pre answer ************/
+
+   
+  if(screen->w < 800 && screen->h < 600)
+  {
+    sprintf(str, "%.3d", num);
+    draw_numbers(str, ((screen->w)/2) - 50, (screen->h) - 30);
+  } 
+  else
+  {
+    FF_draw_led_console();
+    draw_console_image(tux_img);
+  }
+
+  /************** Draw lives ***************/
+  dest.y = screen->h;
+  dest.x = 0;
+
+  for(i = 1; i <= tuxship.lives; i++)
+  {
+    if(tuxship.lives <= 5)
+    {
+      dest.y = dest.y - (images[IMG_TUX_LITTLE]->h);
+      SDL_BlitSurface(images[IMG_TUX_LITTLE], NULL, screen, &dest);
+    }
+    else if(tuxship.lives > 4)
+    {
+      dest.y = screen->h - (images[IMG_TUX_LITTLE]->h);
+      SDL_BlitSurface(images[IMG_TUX_LITTLE], NULL, screen, &dest);
+      sprintf(str, "%d", tuxship.lives);
+      draw_numbers(str, 10, (screen->h) - 30); 
+    }
+  }
+}
+
+/*Modified from game.c*/
+void FF_draw_led_console(void)
+{
+  int i;
+  SDL_Rect src, dest;
+  int y;
+
+  /* draw new console image with "monitor" for LED numbers: */
+  draw_console_image(IMG_CONSOLE_LED);
+  /* set y to draw LED numbers into Tux's "monitor": */
+  y = (screen->h
+     - images[IMG_CONSOLE_LED]->h
+     + 4);  /* "monitor" has 4 pixel margin */
+
+  /* begin drawing so as to center display depending on whether minus */
+  /* sign needed (4 digit slots) or not (3 digit slots) DSB */
+  if (MC_GetOpt(ALLOW_NEGATIVES) )
+    dest.x = ((screen->w - ((images[IMG_LEDNUMS]->w) / 10) * 4) / 2);
+  else
+    dest.x = ((screen->w - ((images[IMG_LEDNUMS]->w) / 10) * 3) / 2);
+
+  for (i = -1; i < MC_MAX_DIGITS; i++) /* -1 is special case to allow minus sign */
+                              /* with minimal modification of existing code DSB */
+  {
+    if (-1 == i)
+    {
+      if (MC_GetOpt(ALLOW_NEGATIVES))
+      {
+        if (neg_answer_picked)
+          src.x =  (images[IMG_LED_NEG_SIGN]->w) / 2;
+        else
+          src.x = 0;
+
+        src.y = 0;
+        src.w = (images[IMG_LED_NEG_SIGN]->w) / 2;
+        src.h = images[IMG_LED_NEG_SIGN]->h;
+
+        dest.y = y;
+        dest.w = src.w;
+        dest.h = src.h;
+
+        SDL_BlitSurface(images[IMG_LED_NEG_SIGN], &src, screen, &dest);
+        /* move "cursor" */
+        dest.x += src.w;
+      }
+    }
+    else
+    {
+      src.x = digits[i] * ((images[IMG_LEDNUMS]->w) / 10);
+      src.y = 0;
+      src.w = (images[IMG_LEDNUMS]->w) / 10;
+      src.h = images[IMG_LEDNUMS]->h;
+
+      /* dest.x already set */
+      dest.y = y;
+      dest.w = src.w;
+      dest.h = src.h;
+
+      SDL_BlitSurface(images[IMG_LEDNUMS], &src, screen, &dest);
+      /* move "cursor" */
+      dest.x += src.w;
+    }
+  }
+}
+
+/* Draw image at lower center of screen: */
+void draw_console_image(int i)
+{
+  SDL_Rect dest;
+
+  dest.x = (screen->w - images[i]->w) / 2;
+  dest.y = (screen->h - images[i]->h);
+  dest.w = images[i]->w;
+  dest.h = images[i]->h;
+
+  SDL_BlitSurface(images[i], NULL, screen, &dest);
+}
+
+static void FF_draw_bkgr(void)
+{
+
+  SDL_BlitSurface(current_bkgd(), NULL, screen, NULL);
+  //if(bgSrc.y>bkg_h)
+  //  SDL_BlitSurface(images[BG_STARS], NULL, screen, &bgScreen);
+
+}
+
+/*Tree rectangle vs a point collitions
+  returns 1 if the collitions is detected
+  and 0 if not*/
+
+int AsteroidColl(int astW,int astH,int astX,int astY,
+                 int x, int y)
+{
+  int astWq=astW/8;
+  int astHq=astH/8;
+  int x1, y1, x2, y2;
+
+  x1=astX+astWq*3;
+  y1=astY;
+  
+  x2=astX+astWq*6;
+  y2=astY+astH;
+
+  if(x>x1 && x<x2 && y>y1 && y<y2)
+    return 1;
+
+  x1=astX;
+  y1=astY+astHq*3;
+  
+  x2=astW;
+  y2=astY+astHq*6;
+
+  if(x>x1 && x<x2 && y>y1 && y<y2)
+    return 1;
+
+  x1=astX+astWq;
+  y1=astY+astHq;
+  
+  x2=astX+astWq*7;
+  y2=astY+astHq*7;
+
+  if(x>x1 && x<x2 && y>y1 && y<y2)
+    return 1;
+
+  return 0;
+}
+
+// Returns x % w but in the range [-w/2, w/2]
+static int modwrap(int x,int w)
+{
+  x = x % w;
+  if (x > (w/2))
+    x -= w;
+  else if (x < -(w/2))
+    x += w;
+  return x;
+}
+
+static void FF_add_level(void)
+{
+  int i = 0;
+  int x, y, xvel, yvel, dx, dy;
+  int ok;
+  int width;
+  int safety_radius2, speed2;
+  int max_speed;
+  Uint32 now_time, last_time;
+  SDL_Rect rect;
+
+  last_time = now_time = SDL_GetTicks();
+
+  wave++;
+  
+  // New lives per wave!
+  if (wave%5==0)
+  {
+    tuxship.lives++;
+  }
+  
+  //Limit the new asteroids
+  if(NUM_ASTEROIDS<MAX_ASTEROIDS)
+     NUM_ASTEROIDS=NUM_ASTEROIDS+wave;
+  else
+     NUM_ASTEROIDS=MAX_ASTEROIDS;
+  
+  width = screen->w;
+  if (screen->h < width)
+    width = screen->h;
+
+  // Define the "safety radius" as one third of the screen width
+  safety_radius2 = width/3;
+  safety_radius2 = safety_radius2*safety_radius2; // the square distance
+  
+  // Define the max speed in terms of the screen width
+  max_speed = width/100;
+  if (max_speed == 0)
+    max_speed = 1;
+
+  for (i=0; i<MAX_ASTEROIDS; i++)
+    asteroid[i].alive=0;
+  for (i=0; i<NUM_ASTEROIDS && NUM_ASTEROIDS<MAX_ASTEROIDS; i++){
+    // Generate the new position, avoiding the location of the ship
+    ok = 0;
+    while (!ok) {
+      x = rand()%(screen->w);
+      y = rand()%(screen->h);
+      dx = modwrap(x - tuxship.x,screen->w);
+      dy = modwrap(y - tuxship.y,screen->h);
+      if (dx*dx + dy*dy > safety_radius2)
+	ok = 1;
+    }
+    // Generate the new speed, making none of them stationary but none
+    // of them too fast
+    ok = 0;
+    while (!ok) {
+      xvel = rand()%(2*max_speed+1) - max_speed;
+      yvel = rand()%(2*max_speed+1) - max_speed;
+      speed2 = xvel*xvel + yvel*yvel;
+      if (speed2 != 0 && speed2 < max_speed*max_speed)
+	ok = 1;
+    }
+   //int FF_add_asteroid(int x, int y, int xspeed, int yspeed, int size, int angle, int angle_speed, int fact_number, int a, int b, int new_wave)
+   if(FF_game == FACTOROIDS_GAME){
+     FF_add_asteroid(x,y,
+		    xvel,yvel,
+		    rand()%2,
+		    rand()%360, rand()%3,
+		    (rand()%(31+(wave*wave))), 
+		    0, 0,
+		    1);
+   }
+   else if(FF_game==FRACTIONS_GAME){
+     FF_add_asteroid(x,y,
+		     xvel,yvel,
+                     rand()%2, 
+		     rand()%360, rand()%3,
+                     0, 
+		     (rand()%(31+(wave*2))), (rand()%(80+(wave*wave))),
+		     1);
+   }
+  }
+
+  if(wave != 1)
+  {
+    while(i < 35)
+    {
+      i++;
+      rect.x=(screen->w/2)-(images[IMG_GOOD]->w/2);
+      rect.y=(screen->h/2)-(images[IMG_GOOD]->h/2);
+      FF_draw();
+      SDL_BlitSurface(images[IMG_GOOD],NULL,screen,&rect);
+      SDL_Flip(screen);
+
+      last_time = now_time;
+      now_time = SDL_GetTicks();
+
+      if (now_time < last_time + MS_PER_FRAME)
+      {
+        now_time = (last_time + MS_PER_FRAME) - now_time;  // this holds the delay
+        if (now_time > MS_PER_FRAME)
+ 	  now_time = MS_PER_FRAME;
+        SDL_Delay(now_time);
+      }
+    }
+  }
+}
+
+static int FF_over(int game_status)
+{
+  Uint32 last_time, now_time; 
+  SDL_Rect dest_message;
+  SDL_Event event;
+
+#ifdef TUXMATH_DEBUG
+  //print_exit_conditions();
+#endif
+
+  /* TODO: need better "victory" screen with animation, special music, etc., */
+  /* as well as options to review missed questions, play again using missed  */
+  /* questions as question list, etc.                                        */
+  switch (game_status)
+  {
+    case GAME_OVER_WON:
+    {
+      int looping = 1;
+//      int frame;
+      /* set up victory message: */
+      dest_message.x = (screen->w - images[IMG_GAMEOVER_WON]->w) / 2;
+      dest_message.y = (screen->h - images[IMG_GAMEOVER_WON]->h) / 2;
+      dest_message.w = images[IMG_GAMEOVER_WON]->w;
+      dest_message.h = images[IMG_GAMEOVER_WON]->h;
+
+      do
+      {
+        //frame++;
+        last_time = SDL_GetTicks();
+
+        /* draw flashing victory message: */
+        //if (((frame / 2) % 4))
+        //{
+          SDL_BlitSurface(images[IMG_GAMEOVER_WON], NULL, screen, &dest_message);
+        //}
+
+
+        SDL_Flip(screen);
+
+        while (1)
+        {
+	  SDL_PollEvent(&event);
+          if  (event.type == SDL_QUIT
+            || event.type == SDL_KEYDOWN
+            || event.type == SDL_MOUSEBUTTONDOWN)
+          {
+            looping = 0;
+	    break;
+          }
+        }
+
+        now_time = SDL_GetTicks();
+
+        if (now_time < last_time + MS_PER_FRAME)
+	  SDL_Delay(last_time + MS_PER_FRAME - now_time);
+      }
+      while (looping);
+      break;
+    }
+
+    case GAME_OVER_ERROR:
+    {
+#ifdef TUXMATH_DEBUG
+      printf("\ngame() exiting with error");
+#endif
+    }
+    case GAME_OVER_LOST:
+    case GAME_OVER_OTHER:
+    {
+      int looping = 1;
+
+      /* set up GAMEOVER message: */
+      dest_message.x = (screen->w - images[IMG_GAMEOVER]->w) / 2;
+      dest_message.y = (screen->h - images[IMG_GAMEOVER]->h) / 2;
+      dest_message.w = images[IMG_GAMEOVER]->w;
+      dest_message.h = images[IMG_GAMEOVER]->h;
+
+      do
+      {
+        //frame++;
+        last_time = SDL_GetTicks();
+
+        SDL_BlitSurface(images[IMG_GAMEOVER], NULL, screen, &dest_message);
+        SDL_Flip(screen);
+
+        while (1)
+        {
+	  SDL_PollEvent(&event);
+          if  (event.type == SDL_QUIT
+            || event.type == SDL_KEYDOWN
+            || event.type == SDL_MOUSEBUTTONDOWN)
+          {
+            looping = 0;
+	    break;
+          }
+        }
+
+        now_time = SDL_GetTicks();
+
+        if (now_time < last_time + MS_PER_FRAME)
+	  SDL_Delay(last_time + MS_PER_FRAME - now_time);
+      }
+      while (looping);
+
+      break;
+    }
+
+    case GAME_OVER_ESCAPE:
+    {
+      break;
+    }
+
+    case GAME_OVER_WINDOW_CLOSE:
+    {
+      break;
+    }
+
+  }
+
+  FF_exit_free();
+
+  /* Save score in case needed for high score table: */
+  Opts_SetLastScore(score);
+
+  /* Return the chosen command: */
+  if (GAME_OVER_WINDOW_CLOSE == game_status)
+  {
+    /* program exits: */
+    FF_exit_free();;
+    return 1;
+  }
+  else
+  {
+    /* return to title() screen: */
+    return 0;
+  }
+}
+
+
+static void FF_exit_free()
+{
+  int i = 0;
+
+  free(asteroid);
+
+  for(i = 0; i < NUM_OF_ROTO_IMGS; i++)
+  {
+    if (IMG_tuxship[i])
+    {
+      SDL_FreeSurface(IMG_tuxship[i]);
+      IMG_tuxship[i] = NULL;
+    }
+    if (IMG_asteroids1[i])
+    {
+      SDL_FreeSurface(IMG_asteroids1[i]);
+      IMG_asteroids1[i] = NULL;
+    }
+    if (IMG_asteroids2[i])
+    {
+      SDL_FreeSurface(IMG_asteroids2[i]);
+      IMG_asteroids2[i] = NULL;
+    }
+  }
+
+//  SDL_FreeSurface(*IMG_asteroids1);
+//  SDL_FreeSurface(*IMG_asteroids2);
+//  SDL_FreeSurface(*IMG_tuxship);
+
+  if (bkgd)
+  {
+    SDL_FreeSurface(bkgd);
+    bkgd = NULL;
+  }
+  if (scaled_bkgd)
+  {
+    SDL_FreeSurface(scaled_bkgd);
+    scaled_bkgd = NULL;
+  }
+}
+
+/******************* Math Funcs ***********************/
+
+/* Return 1 if the number is prime and 0 if its not */
+int is_prime(int num)
+{
+  int i;
+  if (num==0 || num==1 || num==-1) return 1;
+  else if (num > 0)
+  {
+
+    for(i = 2; i < num; i++)
+    {
+      if(num%i == 0) return 0; 
+    }
+  }
+  else if (num < 0)
+  {
+    for(i = 2; i > num; i--)
+    {
+      if(num%i == 0) return 0; 
+    } 
+  }
+  return 1;
+}
+
+int is_simplified(int a, int b)
+{
+  int i;
+  for(i=2; i<1000; i++)
+    if(((a%i)==0)&&((b%i)==0))
+      return 0;
+  return 1;
+}
+/*** Fast cos by Bill***/
+
+int fast_cos(int angle)
+{
+  angle = (angle % 45);
+  
+  if (angle < 12)
+    return(trig[angle]);
+  else if (angle < 23)
+    return(-trig[10 - (angle - 12)]);
+  else if (angle < 34)
+    return(-trig[angle - 22]);
+  else
+    return(trig[45 - angle]);
+}
+
+
+/*** Sine based on fast cosine..., by Bill ***/
+
+int fast_sin(int angle)
+{
+  return(- fast_cos((angle + 11) % 45));
+}
+
+/******************* LASER FUNCTIONS *********************/
+
+/*Return -1 if no laser is available*/
+int FF_add_laser(void)
+{
+  int i, k, zapIndex;
+  float ux, uy, s, smin,dx,dy,dx2, dy2, d2, thresh;
+  int screensize;
+  SDL_Surface *asteroid_image;
+
+  const float inside_factor = 0.9*0.9;
+
+  screensize = screen->w;
+  if (screensize < screen->h)
+    screensize = screen->h;
+
+  for(i=0; i<=MAX_LASER; i++)
+  {
+    if(laser[i].alive==0)
+    {
+      // Fire the laser
+      laser[i].alive=1;
+      laser[i].x=tuxship.centerx;
+      laser[i].y=tuxship.centery;
+      laser[i].angle=tuxship.angle;
+      laser[i].count=15;
+      
+      ux = cos((float)laser[i].angle * DEG_TO_RAD);
+      uy = -sin((float)laser[i].angle * DEG_TO_RAD);
+      laser[i].destx = laser[i].x + (int)(ux * screensize);
+      laser[i].desty = laser[i].y + (int)(uy * screensize);
+      
+      // Check to see if it hits asteroids---we only check when it
+      // just starts firing, "drift" later doesn't count!
+      // We describe the laser path as p = p0 + s*u, where
+      //   p0 = (x0,y0) is the initial position vector (i.e., the ship)
+      //   u = (ux,uy) is the unit vector of the laser's direction
+      //   s (a scalar) is the distance along the laser (s >= 0)
+      // With this parametrization, it's easy to calculate the
+      // closest approach to the asteroid center, etc.
+      zapIndex = -1;  // keep track of the closest "hit" asteroid
+      smin = 10*screensize;
+      for (k=0; k<MAX_ASTEROIDS; k++)
+      {
+	if (!asteroid[k].alive)
+	  continue;
+	asteroid_image = get_asteroid_image(asteroid[k].size,asteroid[k].angle);
+	dx = asteroid[k].x + asteroid_image->w/2 - laser[i].x;
+	dy = asteroid[k].y + asteroid_image->h/2 - laser[i].y;
+	// Find distance along laser of closest approach to asteroid center
+	s = dx*ux + dy*uy;
+	if (s >= 0)  // don't worry about it if it's in the opposite direction! (i.e., behind the ship)
+	{
+	  // Find the distance to the asteroid center at closest approach
+	  dx2 = dx - s*ux;
+	  dy2 = dy - s*uy;
+	  d2 = dx2*dx2 + dy2*dy2;
+	  thresh = (asteroid_image->h)/2;
+	  thresh = thresh*thresh*inside_factor;
+	  if (d2 < thresh)
+	  {
+	    // The laser intersects the asteroid. Check to see if
+	    // the answer works
+	    if((asteroid[k].isprime && ((num==asteroid[k].fact_number)||(num==0))) ||
+	       (FF_game==FACTOROIDS_GAME && num > 1 && ((asteroid[k].fact_number%num)==0) && (num!=asteroid[k].fact_number)) ||
+	       (FF_game==FRACTIONS_GAME && num > 1 && ((asteroid[k].a%num)==0) && ((asteroid[k].a%num)==0) && (num!=asteroid[k].fact_number)))
+	    {
+	      // It's valid, check to see if it's closest
+	      if (s < smin)
+	      {
+		// It's the closest yet examined
+		smin = s;
+		zapIndex = k;
+	      }
+	    }
+	  }
+	}
+      }
+      
+      // Handle the destruction, score, and extra lives
+      if (zapIndex >= 0)  // did we zap one?
+      {
+	isdead = 1;
+	laser[i].destx = laser[i].x + (int)(ux * smin);
+	laser[i].desty = laser[i].y + (int)(uy * smin);
+	FF_destroy_asteroid(zapIndex,2*ux,2*uy);
+	playsound(SND_SIZZLE);
+
+	if (floor((float)score/100) < floor((float)(score+num)/100))
+	  tuxship.lives++;
+	score += num;
+      }
+      return 1;
+    }
+  }
+  fprintf(stderr, "Laser could't be created!\n");
+  return -1;
+}
+
+/******************* ASTEROIDS FUNCTIONS *******************/
+
+
+
+static int FF_add_asteroid(int x, int y, int xspeed, int yspeed, int size, int angle, int angle_speed, int fact_number, int a, int b, int new_wave)
+{
+  int i;
+  for(i=0; i<MAX_ASTEROIDS; i++){
+    if(asteroid[i].alive==0)
+    {
+      asteroid[i].alive=1;
+      asteroid[i].rx=x;
+      asteroid[i].ry=y;
+      asteroid[i].angle=angle;
+      asteroid[i].angle_speed=angle_speed;
+      asteroid[i].y=(asteroid[i].ry - (IMG_tuxship[asteroid[i].angle/DEG_PER_ROTATION]->h/2));
+      asteroid[i].x=(asteroid[i].rx - (IMG_tuxship[asteroid[i].angle/DEG_PER_ROTATION]->w/2));
+      asteroid[i].yspeed=yspeed;
+      asteroid[i].xspeed=xspeed;
+      
+      if(FF_game==FACTOROIDS_GAME){
+
+         asteroid[i].fact_number=fact_number;
+
+  	 while(!asteroid[i].fact_number)
+	   asteroid[i].fact_number=rand()%80;
+
+         asteroid[i].isprime=is_prime(asteroid[i].fact_number);
+
+      }else if(FF_game==FRACTIONS_GAME){
+
+         asteroid[i].a=a;
+         asteroid[i].b=b;
+
+ 	 while(!asteroid[i].a)
+	   asteroid[i].a=rand()%80;
+	 while(!asteroid[i].b)
+	   asteroid[i].b=rand()%80;
+
+	 asteroid[i].isprime=is_simplified(asteroid[i].a,asteroid[i].b);
+      }
+
+      if(new_wave){
+         if(tuxship.x-50<asteroid[i].x+80 && 
+            tuxship.x+50>asteroid[i].x && 
+            tuxship.y-50<asteroid[i].y+80 && 
+            tuxship.y+50>asteroid[i].y &&
+            tuxship.lives>0 &&
+            asteroid[i].alive){ 
+	       asteroid[i].rx=asteroid[i].rx+300;
+	       asteroid[i].ry=asteroid[i].ry+300;
+	    }
+      }
+
+      if(asteroid[i].isprime)
+      {
+        asteroid[i].size=0;
+        asteroid[i].centerx=(images[IMG_ASTEROID1]->w/2)+asteroid[i].x;
+        asteroid[i].centery=(images[IMG_ASTEROID1]->h/2)+asteroid[i].y;
+        asteroid[i].radius=(images[IMG_ASTEROID1]->h/2);
+
+      }
+      else if(!asteroid[i].isprime)
+      {
+        asteroid[i].size=1;
+        asteroid[i].centerx=(images[IMG_ASTEROID2]->w/2)+asteroid[i].x;
+        asteroid[i].centery=(images[IMG_ASTEROID2]->h/2)+asteroid[i].y;
+        asteroid[i].radius=(images[IMG_ASTEROID1]->h/2);
+      }
+       
+      while (asteroid[i].xspeed==0)
+      {
+        asteroid[i].xspeed = ((rand() % 3) - 1)*2;
+      }
+      return 1;
+    }
+  }
+  fprintf(stderr, "Asteroid could't be created!\n");
+  return -1;
+}
+
+int FF_destroy_asteroid(int i, float xspeed, float yspeed)
+{
+  if(asteroid[i].alive==1){
+    isdead=1;
+    xdead=asteroid[i].x;
+    ydead=asteroid[i].y;
+     if(asteroid[i].size>0){
+      /* Break the rock into two smaller ones! */
+      if(num!=0){
+
+
+//static int FF_add_asteroid(int x, int y, int xspeed, int yspeed, int size, int angle, int
+//                           angle_speed, int fact_number, int a, int b, int new_wave
+
+        if(FF_game==FACTOROIDS_GAME){
+          FF_add_asteroid(asteroid[i].rx,
+	  	          asteroid[i].ry,
+	  	          asteroid[i].xspeed + (xspeed - yspeed)/2,
+	  	          asteroid[i].yspeed + (yspeed + xspeed)/2,
+	  	          0,
+	  	          rand()%360, rand()%3, (int)(asteroid[i].fact_number/num),
+		          0, 0,
+                          0);
+      
+          FF_add_asteroid(asteroid[i].rx,
+	  	          asteroid[i].ry,
+	  	          asteroid[i].xspeed + (xspeed + yspeed)/2,
+	  	          asteroid[i].yspeed + (yspeed - xspeed)/2,
+	  	          0,
+	  	          rand()%360, rand()%3, num,
+                          0, 0,
+                          0);
+        }
+        else if(FF_game==FRACTIONS_GAME){
+          FF_add_asteroid(asteroid[i].rx,
+	  	          asteroid[i].ry,
+	  	          ((asteroid[i].xspeed + xspeed) / 2),
+	  	          (asteroid[i].yspeed + yspeed),
+	  	          0,
+	  	          rand()%360, rand()%3, 0,
+		          (int)(asteroid[i].a/num), (int)(asteroid[i].b/num),
+                          0);
+      
+          FF_add_asteroid(asteroid[i].rx,
+	  	          asteroid[i].ry,
+	  	          (asteroid[i].xspeed + xspeed),
+	  	          ((asteroid[i].yspeed + yspeed) / 2),
+	  	          0,
+	  	          rand()%360, rand()%3, 0,
+                          (int)(asteroid[i].b/num), (int)(asteroid[i].a/num),
+                          0); 
+	}
+      } 
+    }
+
+    /* Destroy the old asteroid */
+
+    asteroid[i].alive=0;		  
+    return 1;
+  }
+  return 0;
+}
+
+/************** MODIFIED FUNCS FROM game.c and titlescreen.c ******************/
+
+void FF_ShowMessage(char* str1, char* str2, char* str3, char* str4)
+{
+  SDL_Surface *s1, *s2, *s3, *s4;
+  SDL_Rect loc;
+
+  s1 = s2 = s3 = s4 = NULL;
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "ShowMessage() - creating text\n" );
+#endif
+
+  if (str1)
+    s1 = BlackOutline(str1, default_font, &white);
+  if (str2)
+    s2 = BlackOutline(str2, default_font, &white);
+  if (str3)
+    s3 = BlackOutline(str3, default_font, &white);
+  /* When we get going with i18n may need to modify following - see below: */
+  if (str4)
+    s4 = BlackOutline(str4, default_font, &white);
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "NotImplemented() - drawing screen\n" );
+#endif
+
+
+  /* Draw lines of text (do after drawing Tux so text is in front): */
+  if (s1)
+  {
+    loc.x = (screen->w / 2) - (s1->w/2); 
+    loc.y = (screen->h / 2) + 10;
+    SDL_BlitSurface( s1, NULL, screen, &loc);
+  }
+  if (s2)
+  {
+    loc.x = (screen->w / 2) - (s2->w/2); 
+    loc.y = (screen->h / 2) + 80;
+    SDL_BlitSurface( s2, NULL, screen, &loc);
+  }
+  if (s3)
+  {
+    loc.x = (screen->w / 2) - (s3->w/2); 
+    loc.y = (screen->h / 2) + 130;
+    SDL_BlitSurface( s3, NULL, screen, &loc);
+  }
+  if (s4)
+  {
+    loc.x = (screen->w / 2) - (s4->w/2); 
+    loc.y = (screen->h / 2) + 180;
+    SDL_BlitSurface( s4, NULL, screen, &loc);
+  }
+
+  /* and update: */
+  SDL_UpdateRect(screen, 0, 0, 0, 0);
+
+
+  SDL_FreeSurface(s1);
+  SDL_FreeSurface(s2);
+  SDL_FreeSurface(s3);
+  SDL_FreeSurface(s4);
+}
+
+
+void game_handle_user_events(void)
+{
+  SDL_Event event;
+  SDLKey key;
+
+  while (SDL_PollEvent(&event) > 0)
+  {
+    if (event.type == SDL_QUIT)
+    {
+      SDL_quit_received = 1;
+      quit = 1;
+    }
+    if (event.type == SDL_MOUSEBUTTONDOWN)
+    {
+      key = game_mouse_event(event);
+    }
+    if (event.type == SDL_KEYDOWN ||
+	event.type == SDL_KEYUP)
+    {
+      key = event.key.keysym.sym;
+      
+      if (event.type == SDL_KEYDOWN)
+	{
+	  if (key == SDLK_ESCAPE)
+	  {
+            // Return to menu! 
+            escape_received = 1;
+
+	  }
+	  
+	  // Key press... 
+	 
+	  if (key == SDLK_RIGHT)
+	    {
+	      // Rotate CW 
+	      
+ 	      left_pressed = 0;
+	      right_pressed = 1;
+	    }
+	  else if (key == SDLK_LEFT)
+	    {
+	      // Rotate CCW 
+	      
+	      left_pressed = 1;
+	      right_pressed = 0;
+	    }
+	  else if (key == SDLK_UP)
+	    {
+	      // Thrust! 
+	      
+	      up_pressed = 1;
+	    }
+	  
+	  if (key == SDLK_LSHIFT || key == SDLK_RSHIFT)
+	    {
+	      // Respawn now (if applicable) 
+	      shift_pressed = 1;
+	    }
+
+	  if (key == SDLK_TAB || key == SDLK_p)
+  	{
+    /* [TAB] or [P]: Pause! (if settings allow) */
+    	  if (Opts_AllowPause())
+    	  {
+    	    paused = 1;
+    	  }
+  	}
+  /* The rest of the keys control the numeric answer console: */
+	      
+  if (key >= SDLK_0 && key <= SDLK_9)
+  {
+    /* [0]-[9]: Add a new digit: */
+    digits[0] = digits[1];
+    digits[1] = digits[2];
+    digits[2] = key - SDLK_0;
+    tux_pressing = 1;
+    playsound(SND_SHIELDSDOWN);
+  }
+  else if (key >= SDLK_KP0 && key <= SDLK_KP9)
+  {
+    /* Keypad [0]-[9]: Add a new digit: */
+    digits[0] = digits[1];
+    digits[1] = digits[2];
+    digits[2] = key - SDLK_KP0;
+    tux_pressing = 1;
+    playsound(SND_SHIELDSDOWN);
+  }
+  /* support for negative answer input DSB */
+  else if ((key == SDLK_MINUS || key == SDLK_KP_MINUS))
+        //&& MC_AllowNegatives())  /* do nothing unless neg answers allowed */
+  {
+    /* allow player to make answer negative: */
+    neg_answer_picked = 1;
+    tux_pressing = 1;
+    playsound(SND_SHIELDSDOWN);
+  }
+  else if ((key == SDLK_PLUS || key == SDLK_KP_PLUS))
+         //&& MC_AllowNegatives())  /* do nothing unless neg answers allowed */
+  {
+    /* allow player to make answer positive: */
+    neg_answer_picked = 0;
+    tux_pressing = 1;
+    playsound(SND_SHIELDSDOWN);
+  }
+  else if (key == SDLK_BACKSPACE ||
+           key == SDLK_CLEAR ||
+	   key == SDLK_DELETE)
+  {
+    /* [BKSP]: Clear digits! */
+    digits[0] = 0;
+    digits[1] = 0;
+    digits[2] = 0;
+    tux_pressing = 1;
+    playsound(SND_SHIELDSDOWN);
+  }
+ 	else if (key == SDLK_RETURN ||
+        	   key == SDLK_KP_ENTER ||
+		   key == SDLK_SPACE)
+ 	 {
+	       shoot_pressed = 1;
+               doing_answer = 1;
+	       playsound(SND_LASER);
+  }
+
+
+	  }
+      else if (event.type == SDL_KEYUP)
+	{
+	  // Key release... 
+	  
+	  if (key == SDLK_RIGHT)
+	    {
+	      right_pressed = 0;
+	    }
+	  else if (key == SDLK_LEFT)
+	    {
+               left_pressed = 0;
+ 	    }
+	  else if (key == SDLK_UP)
+	    {
+	      up_pressed = 0;
+	    }
+	  if (key == SDLK_LSHIFT ||
+	      key == SDLK_RSHIFT)
+	    {
+	      // Respawn now (if applicable) 
+	      shift_pressed = 0;
+	    }
+	}
+    }
+
+#ifdef JOY_YES
+	  else if (event.type == SDL_JOYBUTTONDOWN &&
+		   player_alive)
+	    {
+	      if (event.jbutton.button == JOY_B)
+		{
+                  shoot_pressed = 1;
+		}
+	      else if (event.jbutton.button == JOY_A)
+		{
+		  // Thrust:
+		  
+		  up_pressed = 1;
+		}
+	      else
+		{
+		  shift_pressed = 1;
+		}
+	    }
+	  else if (event.type == SDL_JOYBUTTONUP)
+	    {
+	      if (event.jbutton.button == JOY_A)
+		{
+		  // Stop thrust: 
+		  
+		  up_pressed = 0;
+		}
+	      else if (event.jbutton.button != JOY_B)
+		{
+		  shift_pressed = 0;
+		}
+	    }
+	  else if (event.type == SDL_JOYAXISMOTION)
+	    {
+	      if (event.jaxis.axis == JOY_X)
+		{
+		  if (event.jaxis.value < -256)
+		    {
+		      left_pressed = 1;
+		      right_pressed = 0;
+		    }
+		  else if (event.jaxis.value > 256)
+		    {
+		      left_pressed = 0;
+		      right_pressed = 1;
+		    }
+		  else
+		    {
+		      left_pressed = 0;
+		      right_pressed = 0;
+		    }
+		}
+	  }
+#endif
+
+    }
+  
+}
+
+static int game_mouse_event(SDL_Event event)
+{
+  int keypad_w, keypad_h, x, y, row, column;
+  SDLKey key = SDLK_UNKNOWN;
+
+  keypad_w = 0;
+  keypad_h = 0;
+
+  /* Check to see if user clicked exit button: */
+  /* The exit button is in the upper right corner of the screen: */
+  if ((event.button.x >= (screen->w - images[IMG_STOP]->w))
+    &&(event.button.y <= images[IMG_STOP]->h))
+  {
+    key = SDLK_ESCAPE;
+    //game_key_event(key);
+    escape_received = 1;
+    quit = 1;
+    return -1;
+  } 
+
+  /* get out unless we really are using keypad */
+  if ( level_start_wait 
+    || Opts_DemoMode()
+    || !Opts_GetGlobalOpt(USE_KEYPAD))
+  {
+    return -1;
+  }
+
+
+  /* make sure keypad image is valid and has non-zero dimensions: */
+  /* FIXME maybe this checking should be done once at the start */
+  /* of game() rather than with every mouse click */
+  if (1)//MC_AllowNegatives())
+  {
+    if (!images[IMG_KEYPAD])
+      return -1;
+    else
+    {
+      keypad_w = images[IMG_KEYPAD]->w;
+      keypad_h = images[IMG_KEYPAD]->h;
+    }
+  }
+  else
+  {
+    if (!images[IMG_KEYPAD_NO_NEG])
+      return -1;
+    else
+    {
+      keypad_w = images[IMG_KEYPAD]->w;
+      keypad_h = images[IMG_KEYPAD]->h;
+    }
+  }
+ 
+  if (!keypad_w || !keypad_h)
+  {
+    return -1;
+  }
+
+  
+  /* only proceed if click falls within keypad: */
+  if (!((event.button.x >=
+        (screen->w / 2) - (keypad_w / 2) &&
+        event.button.x <=
+        (screen->w / 2) + (keypad_w / 2) &&
+        event.button.y >= 
+        (screen->h / 2) - (keypad_h / 2) &&
+        event.button.y <=
+        (screen->h / 2) + (keypad_h / 2))))
+  /* click outside of keypad - do nothing */
+  {
+    return -1;
+  }
+  
+  else /* click was within keypad */ 
+  {
+    x = (event.button.x - ((screen->w / 2) - (keypad_w / 2)));
+    y = (event.button.y - ((screen->h / 2) - (keypad_h / 2)));
+ 
+  /* Now determine what onscreen key was pressed */
+  /*                                             */
+  /* The on-screen keypad has a 4 x 4 layout:    */
+  /*                                             */
+  /*    *********************************        */
+  /*    *       *       *       *       *        */
+  /*    *   7   *   8   *   9   *   -   *        */
+  /*    *       *       *       *       *        */
+  /*    *********************************        */
+  /*    *       *       *       *       *        */
+  /*    *   4   *   5   *   6   *       *        */
+  /*    *       *       *       *       *        */
+  /*    *************************   +   *        */
+  /*    *       *       *       *       *        */
+  /*    *   1   *   2   *   3   *       *        */
+  /*    *       *       *       *       *        */
+  /*    *********************************        */
+  /*    *       *                       *        */
+  /*    *   0   *         Enter         *        */
+  /*    *       *                       *        */
+  /*    *********************************        */
+  /*                                             */
+  /*  The following code simply figures out the  */
+  /*  row and column based on x and y and looks  */
+  /*  up the SDlKey accordingly.                 */
+
+    column = x/((keypad_w)/4);
+    row    = y/((keypad_h)/4);
+
+    /* make sure row and column are sane */
+    if (column < 0
+     || column > 3
+     || row    < 0
+     || row    > 3)
+    {
+      printf("\nIllegal row or column value!\n");
+      return -1;
+    }
+
+    /* simple but tedious - I am sure this could be done more elegantly */
+
+    if (0 == row)
+    {
+      if (0 == column)
+        key = SDLK_7;
+      if (1 == column)
+        key = SDLK_8;
+      if (2 == column)
+        key = SDLK_9;
+      if (3 == column)
+        key = SDLK_MINUS;
+    } 
+    if (1 == row)
+    {
+      if (0 == column)
+        key = SDLK_4;
+      if (1 == column)
+        key = SDLK_5;
+      if (2 == column)
+        key = SDLK_6;
+      if (3 == column)
+        key = SDLK_PLUS;
+    }     
+    if (2 == row)
+    {
+      if (0 == column)
+        key = SDLK_1;
+      if (1 == column)
+        key = SDLK_2;
+      if (2 == column)
+        key = SDLK_3;
+      if (3 == column)
+        key = SDLK_PLUS;
+    } 
+    if (3 == row)
+    {
+      if (0 == column)
+        key = SDLK_0;
+      if (1 == column)
+        key = SDLK_RETURN;
+      if (2 == column)
+        key = SDLK_RETURN;
+      if (3 == column)
+        key = SDLK_RETURN;
+    }     
+
+    if (key == SDLK_UNKNOWN)
+    {
+      return -1;
+    }
+
+    /* now can proceed as if keyboard was used */
+    //game_key_event(key);
+    return key;
+  }
+}
+
+
+static int check_exit_conditions(void)
+{
+  if(SDL_quit_received)
+  {
+    return GAME_OVER_WINDOW_CLOSE;
+  }
+
+  if(escape_received)
+  {
+    return GAME_OVER_ESCAPE;
+  }
+  if(tuxship.lives<=0)
+  {
+    return GAME_OVER_LOST;
+  }
+  if(score>=10000 || wave >= 30 )
+  {
+    return GAME_OVER_WON;
+  }
+  /* determine if game lost (i.e. all cities blown up): */
+  /*if (!num_cities_alive)
+  {
+    if (gameover_counter < 0)
+      gameover_counter = GAMEOVER_COUNTER_START;
+    gameover_counter--;
+    if (gameover_counter == 0)
+      return GAME_OVER_LOST;
+  }*/
+  
+  /* determine if game won (i.e. all questions in mission answered correctly): */
+  /*if (MC_MissionAccomplished())
+  {
+    return GAME_OVER_WON;
+  }*/
+
+  /* Could have situation where mathcards doesn't have more questions */
+  /* even though not all questions answered correctly:                */
+  /*if (!MC_TotalQuestionsLeft())
+  {
+    return GAME_OVER_OTHER;
+  }*/
+
+  /* Need to get out if no comets alive and MathCards has no questions left in list, */
+  /* even though MathCards thinks there are still questions "in play".  */
+  /* This SHOULD NOT HAPPEN and means we have a bug somewhere. */
+ /* if (!MC_ListQuestionsLeft() && !num_comets_alive)
+  {
+    #ifdef TUXMATH_DEBUG
+    printf("\nListQuestionsLeft() = %d", MC_ListQuestionsLeft());
+    printf("\nnum_comets_alive = %d", num_comets_alive);
+    #endif 
+    return GAME_OVER_ERROR;
+  }
+  */
+  /* If using demo mode, see if counter has run out: */ 
+  /*if (Opts_DemoMode())
+  {
+    if (demo_countdown <= 0 )
+      return GAME_OVER_OTHER;
+  }*/
+
+  /* if we made it to here, the game goes on! */
+  return GAME_IN_PROGRESS;
+}

Added: tuxmath/trunk/src/factoroids.h
===================================================================
--- tuxmath/trunk/src/factoroids.h	                        (rev 0)
+++ tuxmath/trunk/src/factoroids.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,24 @@
+/************************************************************
+ *  factoroids.h                                             *
+ *                                                          *
+ *  Description: Contains headers for the fractor and       *
+ *               fraction activities.                       *
+ *                                                          *
+ *  Autor:       Jesus M. Mager H. (fongog at gmail.com) 2008  *
+ *  Copyright:   GPL v3 or later                            *
+ *                                                          *
+ *  TuxMath                                                 *
+ *  Part of "Tux4Kids" Project                              *
+ *  http://tux4kids.alioth.debian.org/                      *
+ ************************************************************/
+
+#ifndef FACTOROIDS_H
+#define FACTOROIDS_H
+
+// Used in titleecreen.c
+
+int factors(void);
+int fractions(void);
+
+
+#endif
\ No newline at end of file

Added: tuxmath/trunk/src/fileops.c
===================================================================
--- tuxmath/trunk/src/fileops.c	                        (rev 0)
+++ tuxmath/trunk/src/fileops.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,2391 @@
+/*
+*  C Implementation: fileops.c
+*
+* (Note: read_config_file() was made possible by studying the file prefs.c in gtkpod:
+*  URL: http://www.gtkpod.org/
+*  URL: http://gtkpod.sourceforge.net/
+*  Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net>.
+*  Licensed under GNU GPL v2.
+*  This code is a nearly complete rewrite but I would like to express my thanks.)
+*
+*
+* Description: File operations - together, fileops.h and fileops.c contain 
+* all code involving disk operations.  The intention is to make it easier to
+* port tuxmath to other operating systems, as code to read and write as 
+* well as paths and file locations may be more OS-dependent.
+*
+* This file contains functions to read and write config files.
+* The config file contains name-value pairs, one pair per line, to control
+* settings for the behavior of Tuxmath.
+*
+* Code for loading program data from disk is now also found here.
+* 
+* Author: David Bruce <dbruce at tampabay.rr.com>, (C) 2006
+*
+* Copyright: See COPYING file that comes with this distribution (briefly, GNU GPL)
+*
+*/
+
+#include <stdio.h>
+
+//#include "config.h"
+
+/* Tuxmath includes: */
+#include "globals.h"
+#include "fileops.h"
+#include "setup.h"
+#include "mathcards.h"
+#include "options.h"
+#include "highscore.h"
+#include "lessons.h"
+#include "scandir.h"
+
+/* OS includes - NOTE: these may not be very portable */
+#include <dirent.h>  /* for opendir() */
+#include <sys/stat.h>/* for mkdir() */
+#include <unistd.h>  /* for getcwd() */
+#include <sys/types.h> /* for umask() */
+
+/* Standard C includes: */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+
+
+/* Used by both write_pregame_summary() and */
+/* write_postgame_summary() so defined with */
+/* file scope:                              */
+#ifdef BUILD_MINGW32
+#define SUMMARY_EXTENSION ".txt"
+#else
+#define SUMMARY_EXTENSION ""
+#endif 
+
+static char* summary_filenames[NUM_SUMMARIES] = {
+  "summary1" SUMMARY_EXTENSION,
+  "summary2" SUMMARY_EXTENSION,
+  "summary3" SUMMARY_EXTENSION,
+  "summary4" SUMMARY_EXTENSION,
+  "summary5" SUMMARY_EXTENSION,
+  "summary6" SUMMARY_EXTENSION,
+  "summary7" SUMMARY_EXTENSION,
+  "summary8" SUMMARY_EXTENSION,
+  "summary9" SUMMARY_EXTENSION,
+  "summary10" SUMMARY_EXTENSION
+};
+
+/* local function prototypes: */
+static int find_tuxmath_dir(void);
+static int str_to_bool(const char* val);
+static int read_config_file(FILE* fp, int file_type);
+static int write_config_file(FILE* fp, int verbose);
+static int is_lesson_file(const struct dirent *lfdirent);
+static int read_goldstars(void);
+static int read_lines_from_file(FILE *fp,char ***lines);
+static int parse_option(const char* name, int val, int file_type);
+static void dirname_up(char *dirname);
+static char* get_user_name(void);
+static char* get_file_name(char *fullpath);
+
+
+/* Mingw does not have localtime_r(): */
+/* (this replacement is Windows-specific, so also check for Win32) */
+#ifndef HAVE_LOCALTIME_R
+#ifdef WIN32
+#define localtime_r( _clock, _result ) \
+        ( *(_result) = *localtime( (_clock) ), \
+          (_result) )
+#endif
+#endif
+
+
+/*************************************************************************
+Using Autoconf's "config.h", we include our portability replacements
+for scandir() and alphasort() if necessary:
+*************************************************************************/
+
+#ifndef HAVE_SCANDIR
+#include "scandir.h"
+#endif /* end of scandir() replacements */
+
+/* fix HOME on windows */
+#ifdef BUILD_MINGW32
+#include <windows.h>
+
+
+
+
+
+ 
+/* STOLEN from tuxpaint */
+
+/*
+  Removes a single '\' or '/' from end of path 
+*/
+static char *remove_slash(char *path)
+{
+  int len = strlen(path);
+
+  if (!len)
+    return path;
+
+  if (path[len-1] == '/' || path[len-1] == '\\')
+    path[len-1] = 0;
+
+  return path;
+}
+
+/*
+  Read access to Windows Registry
+*/
+static HRESULT ReadRegistry(const char *key, const char *option, char *value, int size)
+{
+  LONG        res;
+  HKEY        hKey = NULL;
+
+  res = RegOpenKeyEx(HKEY_CURRENT_USER, key, 0, KEY_READ, &hKey);
+  if (res != ERROR_SUCCESS)
+    goto err_exit;
+  res = RegQueryValueEx(hKey, option, NULL, NULL, (LPBYTE)value, (LPDWORD)&size);
+  if (res != ERROR_SUCCESS)
+    goto err_exit;
+  res = ERROR_SUCCESS;
+
+err_exit:
+  if (hKey) RegCloseKey(hKey);
+  return HRESULT_FROM_WIN32(res);
+}
+
+
+/*
+  Returns heap string containing default application data path.
+  Creates suffix subdirectory (only one level).
+  E.g. C:\Documents and Settings\jfp\Application Data\suffix
+*/
+char *GetDefaultSaveDir(const char *suffix)
+{
+  char          prefix[MAX_PATH];
+  char          path[2*MAX_PATH];
+  const char   *key    = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
+  const char   *option = "AppData";
+  HRESULT hr = S_OK;
+
+  if (SUCCEEDED(hr = ReadRegistry(key, option, prefix, sizeof(prefix))))
+  {
+    remove_slash(prefix);
+    snprintf(path, sizeof(path), "%s/%s", prefix, suffix);
+    _mkdir(path);
+    return strdup(path);
+  }
+  return strdup("userdata");
+}
+
+
+/* Windows XP: User/App Data/TuxMath/ */
+/* Windows 98/ME: TuxMath install dir/userdata/Options */
+#define OPTIONS_SUBDIR ""
+#define OPTIONS_FILENAME "options.cfg"
+#define HIGHSCORE_FILENAME "highscores.txt"
+#define GOLDSTAR_FILENAME "goldstars.txt"
+#define USER_MENU_ENTRIES_FILENAME "user_menu_entries.txt"
+#define USER_LOGIN_QUESTIONS_FILENAME "user_login_questions.txt"
+#else
+
+# define get_home getenv("HOME")
+#define OPTIONS_SUBDIR "/.tuxmath"
+#define OPTIONS_FILENAME "options"
+#define HIGHSCORE_FILENAME "highscores"
+#define GOLDSTAR_FILENAME "goldstars"
+#define USER_MENU_ENTRIES_FILENAME "user_menu_entries"
+#define USER_LOGIN_QUESTIONS_FILENAME "user_login_questions"
+
+#endif
+
+
+/* This functions keep and returns the user data directory application path */
+/* FIXME?: currently the best way to test whether we're using the user's    */
+/* home directory, or using a different path, is to test add_subdir (which  */
+/* is 1 if we're using the user's ~/.tuxmath directory, 0 otherwise). Is    */
+/* this a bad example of using 1 thing for 2 purposes? So far there are     */
+/* no conflicts. */
+static char* user_data_dir = NULL;
+static int add_subdir = 1;
+static char* high_scores_file_path = NULL;
+
+/* A variable for storing the "current" config filename */
+static char* last_config_file_name = NULL;
+
+char *get_user_data_dir ()
+{ 
+  if (! user_data_dir)
+#ifdef BUILD_MINGW32
+     user_data_dir = GetDefaultSaveDir(PROGRAM_NAME);
+#else
+     user_data_dir = strdup(getenv("HOME"));
+#endif
+
+  return user_data_dir;  
+}
+
+/* This function sets the user data directory, and also sets a flag
+   indicating that this should function as a .tuxmath directory, and
+   thus doesn't need the subdir appended. */
+void set_user_data_dir(const char *dirname)
+{
+  int len;
+
+  if (user_data_dir != NULL)
+    free(user_data_dir);   // clear the previous setting
+
+  // Allocate space for the directory name. We do it with +2 because
+  // we have to leave room for a possible addition of a "/"
+  // terminator.
+  user_data_dir = (char*) malloc((strlen(dirname)+2)*sizeof(char));
+  if (user_data_dir == NULL) {
+    fprintf(stderr,"Error: insufficient memory for duplicating string %s.\n",dirname);
+    exit(EXIT_FAILURE);
+  }
+  strcpy(user_data_dir,dirname);
+
+  // Check to see that dirname is properly terminated
+  len = strlen(user_data_dir);
+  if (user_data_dir[len-1] != '/')
+    strcat(user_data_dir,"/");
+
+  // If the user supplies a homedir, interpret it literally and don't
+  // add .tuxmath
+  add_subdir = 0;
+}
+
+/* This gets the user data directory including the .tuxmath, if applicable */
+void get_user_data_dir_with_subdir(char *opt_path)
+{
+  strcpy(opt_path, get_user_data_dir());
+  if (add_subdir)
+    strcat(opt_path, OPTIONS_SUBDIR "/");
+}
+  
+/* FIXME should have better file path (/etc or /usr/local/etc) and name */
+int read_global_config_file(void)
+{
+  FILE* fp;
+  fp = fopen(DATA_PREFIX "/missions/options", "r");
+  if (fp)
+  {
+    read_config_file(fp, GLOBAL_CONFIG_FILE);
+    fclose(fp);
+    fp = NULL;
+    return 1;
+  }
+  else
+    return 0;
+}
+
+/* Attempts to read in user's config file - on a *nix system, */
+/* something like: /home/laura/.tuxmath/options               */
+int read_user_config_file(void)
+{
+  FILE* fp;
+  char opt_path[PATH_MAX];
+
+  /* find $HOME and tack on file name: */
+  get_user_data_dir_with_subdir(opt_path);
+  strcat(opt_path, OPTIONS_FILENAME);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn read_user_config_file() full path to config file is: = %s\n", opt_path);
+  #endif
+
+  fp = fopen(opt_path, "r");
+  if (fp) /* file exists */
+  {
+    read_config_file(fp, USER_CONFIG_FILE);
+    fclose(fp);
+    fp = NULL;
+    return 1;
+  }
+  else  /* could not open config file: */
+  {
+    return 0;
+  }
+}
+
+/* Looks for matching file in various locations:        */
+/*   1. Current working directory                       */
+/*   2. As an absolute path filename                    */
+/*   3. In tuxmath's missions directory.                */
+/*   4. In missions/lessons directory.                  */
+/*   5. In missions/arcade directory.                   */
+/*   6. In user's own .tuxmath directory                */
+/* FIXME redundant code - figure out way to iterate through above */
+int read_named_config_file(const char* fn)
+{
+  FILE* fp;
+  char opt_path[PATH_MAX];
+  /* Make compiler happy: */
+  const char* filename = (const char*)fn;
+
+  if (last_config_file_name != NULL)
+    free(last_config_file_name);
+  last_config_file_name = strdup(filename);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn read_named_config_file() filename is: = %s\n", filename);
+  #endif
+
+
+  /* First look in current working directory:  */
+  getcwd(opt_path, PATH_MAX); /* get current working directory */
+  /* add separating '/' unless cwd is '/' : */
+  if (0 != strcmp("/", opt_path)) 
+  {
+    strcat(opt_path, "/");
+  }
+  strcat(opt_path, filename); /* tack on filename              */
+
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn read_named_config_file() checking for %s (cwd)\n", opt_path);
+  #endif
+
+
+  fp = fopen(opt_path, "r");  /* try to open file */
+  if (fp) /* file exists */
+  {
+    #ifdef TUXMATH_DEBUG
+    printf("\nFound %s\n", opt_path);
+    #endif
+
+    if (read_config_file(fp, USER_CONFIG_FILE))
+    {
+      fclose(fp);
+      fp = NULL;
+      return 1;
+    }
+    else /* try matching filename elsewhere */
+    {
+      fclose(fp);
+      fp = NULL;
+    }
+  }
+
+
+  /* Next try matching filename as absolute:      */
+  /* Supply leading '/' if not already there:   */
+  if (0 == strncmp ("/", filename, 1))
+  { 
+    strcpy(opt_path, filename);
+  }
+  else
+  {
+    strcpy(opt_path, "/");
+    strcat(opt_path, filename);
+  }
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn read_named_config_file() checking for %s (abs)\n", opt_path);
+  #endif
+
+  fp = fopen(opt_path, "r");
+  if (fp) /* file exists */
+  {
+    #ifdef TUXMATH_DEBUG
+    printf("\nFound %s\n", opt_path);
+    #endif
+
+    if (read_config_file(fp, USER_CONFIG_FILE))
+    {
+      fclose(fp);
+      fp = NULL;
+      return 1;
+    }
+    else /* keep trying to match filename elsewhere */
+    {
+      fclose(fp);
+      fp = NULL;
+    }
+  }
+
+
+  /* Next look in missions folder:      */
+  strcpy(opt_path, DATA_PREFIX);
+  strcat(opt_path, "/missions/");
+  strcat(opt_path, filename);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn read_named_config_file() checking for %s (missions)\n", opt_path);
+  #endif
+
+  fp = fopen(opt_path, "r");
+  if (fp) /* file exists */
+  {
+    #ifdef TUXMATH_DEBUG
+    printf("\nFound %s\n", opt_path);
+    #endif
+
+    if (read_config_file(fp, USER_CONFIG_FILE))
+    {
+      fclose(fp);
+      fp = NULL;
+      return 1;
+    }
+    else /* keep trying to match filename elsewhere */
+    {
+      fclose(fp);
+      fp = NULL;
+    }
+  }  
+
+  /* Next look in missions/lessons folder (for prepared "lessons curriculum"):      */
+  strcpy(opt_path, DATA_PREFIX);
+  strcat(opt_path, "/missions/lessons/");
+  strcat(opt_path, filename);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn read_named_config_file() checking for %s (missions/lessons)\n", opt_path);
+  #endif
+
+  fp = fopen(opt_path, "r");
+  if (fp) /* file exists */
+  {
+    #ifdef TUXMATH_DEBUG
+    printf("\nFound %s\n", opt_path);
+    #endif
+
+    if (read_config_file(fp, USER_CONFIG_FILE))
+    {
+      fclose(fp);
+      fp = NULL;
+      return 1;
+    }
+    else /* keep trying to match filename elsewhere */
+    {
+      fclose(fp);
+      fp = NULL;
+    }
+  }  
+
+  /* Next look in missions/arcade folder (for high score competition):      */
+  strcpy(opt_path, DATA_PREFIX);
+  strcat(opt_path, "/missions/arcade/");
+  strcat(opt_path, filename);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn read_named_config_file() checking for %s (missions/arcade)\n", opt_path);
+  #endif
+
+  fp = fopen(opt_path, "r");
+  if (fp) /* file exists */
+  {
+    #ifdef TUXMATH_DEBUG
+    printf("\nFound %s\n", opt_path);
+    #endif
+
+    if (read_config_file(fp, USER_CONFIG_FILE))
+    {
+      fclose(fp);
+      fp = NULL;
+      return 1;
+    }
+    else /* keep trying to match filename elsewhere */
+    {
+      fclose(fp);
+      fp = NULL;
+    }
+  }  
+
+  /* Look in user's hidden .tuxmath directory  */
+  /* find $HOME and tack on file name: */
+  get_user_data_dir_with_subdir(opt_path);
+  strcat(opt_path, filename);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn read_named_config_file() checking for %s (.tuxmath)\n", opt_path);
+  #endif
+
+  fp = fopen(opt_path, "r");
+  if (fp) /* file exists */
+  {
+    #ifdef TUXMATH_DEBUG
+    printf("\nFound %s\n", opt_path);
+    #endif
+
+    if (read_config_file(fp, USER_CONFIG_FILE))
+    {
+      fclose(fp);
+      fp = NULL;
+      return 1;
+    }
+    else /* keep trying to match filename elsewhere */
+    {
+      fclose(fp);
+      fp = NULL;
+    }
+  }
+
+
+  /* Look in user's home directory  */
+  /* find $HOME and tack on file name: */
+  strcpy(opt_path, get_user_data_dir());
+  strcat(opt_path, "/");
+  strcat(opt_path, filename);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn read_named_config_file() checking for %s (home)\n", opt_path);
+  #endif
+
+  fp = fopen(opt_path, "r");
+  if (fp) /* file exists */
+  {
+    #ifdef TUXMATH_DEBUG
+    printf("\nFound %s\n", opt_path);
+    #endif
+
+    if (read_config_file(fp, USER_CONFIG_FILE))
+    {
+      fclose(fp);
+      fp = NULL;
+      return 1;
+    }
+    else /* keep trying to match filename elsewhere */
+    {
+      fclose(fp);
+      fp = NULL;
+    }
+  }
+
+  /* Could not find file (or read it if found) in any location: */
+  #ifdef TUXMATH_DEBUG
+  printf("\nread_named_config_file() could not find/read: %s\n", opt_path);
+  #endif
+  return 0;
+}
+
+/* NOTE the cast to "const char*" just prevents compiler from complaining */
+static int is_lesson_file(const struct dirent *lfdirent)
+{
+  return (0 == strncasecmp((const char*)&(lfdirent->d_name), "lesson", 6));
+  /* FIXME Should somehow test each file to see if it is a tuxmath config file */
+}
+
+
+
+int parse_lesson_file_directory(void)
+{
+  char lesson_path[PATH_MAX];             //Path to lesson directory
+  char* fgets_return_val;
+  char name_buf[NAME_BUF_SIZE];
+  int nchars;
+
+  struct dirent **lesson_list_dirents = NULL;
+  FILE* tempFile = NULL;
+
+  int i = 0;
+  int lessonIterator = 0;  //Iterator over matching files in lesson dir
+  int length = 0;
+  int lessons = 0;         //Iterator over accepted (& parsed) lesson files
+
+  num_lessons = 0;
+
+  /* find the directory containing the lesson files:  */
+  nchars = snprintf(lesson_path, PATH_MAX, "%s/missions/lessons", DATA_PREFIX);
+  if (nchars < 0 || nchars >= PATH_MAX) {
+    perror("formatting lesson directory");
+    return 0;
+  }
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "lesson_path is: %s\n", lesson_path);
+#endif
+
+  /* Believe we now have complete scandir() for all platforms :) */
+  num_lessons = scandir(lesson_path, &lesson_list_dirents, is_lesson_file, alphasort);
+
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "num_lessons is: %d\n", num_lessons);
+#endif
+
+  if (num_lessons < 0) {
+    perror("scanning lesson directory");
+    num_lessons = 0;
+    return 0;
+  }
+
+  /* Allocate storage for lesson list */
+  lesson_list_titles = (char**) malloc(num_lessons * sizeof(char*));
+  lesson_list_filenames = (char**) malloc(num_lessons * sizeof(char*));
+  if (lesson_list_titles == NULL || lesson_list_filenames == NULL) {
+    perror("allocating memory for lesson list");
+    return 0;
+  }
+  for (lessonIterator = 0; lessonIterator < num_lessons; lessonIterator++) {
+    lesson_list_titles[lessonIterator] = (char*) malloc(NAME_BUF_SIZE * sizeof(char));
+    lesson_list_filenames[lessonIterator] = (char*) malloc(NAME_BUF_SIZE * sizeof(char));
+    if (lesson_list_titles[lessonIterator] == NULL || lesson_list_filenames[lessonIterator] == NULL) {
+      perror("allocating memory for lesson filenames or titles");
+      return 0;
+    }
+  }
+
+  /* lessonIterator indexes the direntries, lessons indexes */
+  /* the correctly-parsed files.  If successful in parsing, */
+  /* lessons gets incremented. In case of problems, we can  */
+  /* just continue onto the next entry without incrementing */
+  /* lessons, and the bad entry will get overwritten by the */
+  /* next one (or simply never used, if it was the last).   */
+  for (lessonIterator = 0, lessons = 0; lessonIterator < num_lessons; lessonIterator++) {
+    /* Copy over the filename (as a full pathname) */
+    nchars = snprintf(lesson_list_filenames[lessons], NAME_BUF_SIZE, "%s/%s", lesson_path, lesson_list_dirents[lessonIterator]->d_name);
+    if (nchars < 0 || nchars >= NAME_BUF_SIZE)
+      continue;
+
+#ifdef TUXMATH_DEBUG
+    fprintf(stderr, "Found lesson file %d:\t%s\n", lessons, lesson_list_filenames[lessons]);
+#endif
+
+    /* load the name for the lesson from the file ... (1st line) */
+    tempFile = fopen(lesson_list_filenames[lessons], "r");
+    if (tempFile==NULL)
+    {
+      continue;
+    }
+    fgets_return_val = fgets(name_buf, NAME_BUF_SIZE, tempFile);
+    if (fgets_return_val == NULL) {
+      continue;
+    }
+    fclose(tempFile);
+
+
+    /* check to see if it has a \r at the end of it (dos format!) */
+    length = strlen(name_buf);
+    while (length>0 && (name_buf[length - 1] == '\r' || name_buf[length - 1] == '\n')) {
+      name_buf[length - 1] = '\0';
+      length--;
+    }
+
+    /* Go past leading '#', ';', or whitespace: */
+    /* NOTE getting i to the correct value on exit is the main goal of the loop */
+    for (  i = 0;
+           ((name_buf[i] == '#') ||
+           (name_buf[i] == ';') ||
+           isspace(name_buf[i])) &&
+           (i < NAME_BUF_SIZE);
+           i++  )
+    {
+      length--;
+    }
+    /* Now copy the rest of the first line into the list: */
+    /* Note that "length + 1" is needed so that the final \0 is copied! */
+    memmove(lesson_list_titles[lessons], &name_buf[i], length + 1); 
+
+
+    /* Increment the iterator for correctly-parsed lesson files */
+    lessons++;
+  }
+  /* Now free the individual dirents. We do this on a second pass */
+  /* because of the "continue" approach used to error handling.   */
+  for (lessonIterator = 0; lessonIterator < num_lessons; lessonIterator++)
+    free(lesson_list_dirents[lessonIterator]);
+  free(lesson_list_dirents);
+
+  /* In case we didn't keep all of them, revise our count of how */
+  /* many there are */
+  num_lessons = lessons;
+
+  /* Now we check to see which lessons have been previously completed */
+  /* so we can display the Gold Stars: */
+  /* Allocate storage for lesson list */
+
+  /* prevent memory leak in case we called this already and */
+  /* free the list:                                         */
+  if(lesson_list_goldstars)
+  {
+    free(lesson_list_goldstars);
+    lesson_list_goldstars = NULL;
+  }
+
+  lesson_list_goldstars = (int*)malloc(num_lessons*sizeof(int));
+  if (!lesson_list_goldstars)
+  {
+    perror("unable to allocate memory for gold star list");
+    return 0;
+  }
+  for (i = 0; i < num_lessons; i++)
+  {
+    lesson_list_goldstars[i] = 0;
+  }
+
+  /* Now read file to see what lessons have been previously completed: */
+  read_goldstars();
+
+  return (num_lessons > 0);  /* Success! */
+}
+
+
+/* Look for a completed lessons file in the user's homedir   */
+/* and if found, pass the FILE* to read_goldstars_fp()       */
+/* to actually read the data. The idea is to have TuxMath    */
+/* keep track of what lessons the student has successfully   */
+/* completed and display the "Gold Star" icon for those,     */
+/* versus a grayed-out one for lessons remaining to be done. */
+int read_goldstars(void)
+{
+  FILE* fp;
+  char opt_path[PATH_MAX];
+
+  /* find $HOME and tack on file name: */
+  get_user_data_dir_with_subdir(opt_path);
+  strcat(opt_path, GOLDSTAR_FILENAME);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn read_goldstars() full path to file is: = %s\n", opt_path);
+  #endif
+
+  fp = fopen(opt_path, "r");
+  if (fp) /* file exists */
+  {
+    read_goldstars_fp(fp);
+    fclose(fp);
+    fp = NULL;
+    return 1;
+  }
+  else  /* could not open goldstar file: */
+  {
+    return 0;
+  }
+}
+
+
+/* Write gold star list in user's homedir in format     */
+/* compatible with read_goldstars() above.              */
+int write_goldstars(void)
+{
+  char opt_path[PATH_MAX];
+  FILE* fp;
+
+  if (!find_tuxmath_dir())
+  {
+    fprintf(stderr, "\nCould not find or create tuxmath dir\n");
+    return 0;
+  }
+
+  /* find $HOME and add rest of path to config file: */
+  get_user_data_dir_with_subdir(opt_path);
+  strcat(opt_path, GOLDSTAR_FILENAME);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn write_goldstars() full path to file is: = %s\n", opt_path);
+  #endif
+
+  fp = fopen(opt_path, "w");
+  if (fp)
+  {
+    write_goldstars_fp(fp);
+    fclose(fp);
+    fp = NULL;
+    return 1;
+  }
+  else {
+    fprintf(stderr, "\nUnable to write goldstars file.\n");
+    return 0;
+  }
+}
+
+
+/* Look for a highscore table file in the current user    */
+/* data directory.  Return 1 if found, 0 if not.  This    */
+/* is used for the multi-user login code, in deciding     */
+/* where to put the highscore information.                */
+int high_scores_found_in_user_dir(void)
+{
+  FILE* fp;
+  char opt_path[PATH_MAX];
+
+  /* find $HOME and tack on file name: */
+  get_user_data_dir_with_subdir(opt_path);
+  strcat(opt_path, HIGHSCORE_FILENAME);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn read_high_scores() full path to file is: = %s\n", opt_path);
+  #endif
+
+  fp = fopen(opt_path, "r");
+  if (fp) /* file exists */
+  {
+    fclose(fp);
+    return 1;
+  }
+  else
+    return 0;
+}
+
+/* Set the path to the high score file to the current     */
+/* user data dir                                          */
+void set_high_score_path(void)
+{
+  char opt_path[PATH_MAX];
+
+  /* find $HOME and tack on file name: */
+  get_user_data_dir_with_subdir(opt_path);
+
+  // Free any previous allocation
+  if (high_scores_file_path != NULL)
+    free(high_scores_file_path);
+
+  high_scores_file_path = strdup(opt_path);
+}
+
+/* Look for a high score table file in the user's homedir */
+/* and if found, pass the FILE* to read_high_scores_fp() in */
+/* highscore.c to actually read in scores. (A "global"    */
+/* location might in theory be better, but most schools   */
+/* run Windows with all students sharing a common login   */
+/* that may not be able to write to "global" locations).  */
+int read_high_scores(void)
+{
+  FILE* fp;
+  char opt_path[PATH_MAX];
+
+  /* find $HOME and tack on file name: */
+  if (high_scores_file_path == NULL)
+    get_user_data_dir_with_subdir(opt_path);
+  else
+    strncpy(opt_path,high_scores_file_path,PATH_MAX);
+  strcat(opt_path, HIGHSCORE_FILENAME);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn read_high_scores() full path to file is: = %s\n", opt_path);
+  #endif
+
+  fp = fopen(opt_path, "r");
+  if (fp) /* file exists */
+  {
+    initialize_scores();  // clear any previous values
+    read_high_scores_fp(fp);
+    fclose(fp);
+    fp = NULL;
+    return 1;
+  }
+  else  /* could not open highscore file: */
+  {
+    return 0;
+  }
+}
+
+/* On File Locking: With multiple users possibly updating the same
+   high-scores table simultaneously, we have to be concerned with the
+   possibility that the high score information might change between
+   the time at which it was determined that the user gets a high
+   score, and time at which the high score table actually gets
+   written.  This is especially problematic if it takes kids a while
+   to type in their name, and it's being assumed that the high scores
+   table is valid over that entire time.
+
+   As a first (easy) step, it's best to simply append new information
+   to the high scores file, rather than re-writing the whole file; the
+   read function can make sure that only the top scores are used.
+   That way, the only time there would be trouble is if two appends
+   start at exactly the same moment; and since the amount of
+   information per line is small (and is thus written quickly) and
+   updates are unlikely to be occurring on a
+   millisecond-by-millisecond basis, it's pretty unlikely that
+   problems will crop up.
+
+   An even more robust alternative is to use real file locking.  One
+   would need to design a cross-platform solution that also does
+   sensible things (like, say, delete the lock if it's been held for
+   more than 1s, so that locking doesn't block the application).  In
+   researching this, the best approach seems to be:
+   a) Open a second file - a lock file of a specific name - for read/write.
+   b) If the lock file already contains your process ID, proceed
+   c) If the lock file already contains a different process ID, deny
+   d) If the lock file is new / empty write and flush your process ID
+      to it, then go back to step (a)
+
+   However, given that this information may not be "mission critical"
+   (pun intended) and might be cleared on a somewhat regular basis
+   anyway, it seems reasonable to just use the append strategy.
+*/
+
+/* Append a new high score to the high-scores file.       */
+/* Using this approach is safer than writing the whole    */
+/* high scores table if you're in an environment where    */
+/* multiple users might be updating the table             */
+/* simultaneously.                                        */
+int append_high_score(int tableid,int score,char *player_name)
+{
+  char opt_path[PATH_MAX];
+  FILE* fp;
+
+  if (!find_tuxmath_dir())
+  {
+    fprintf(stderr, "\nCould not find or create tuxmath dir\n");
+    return 0;
+  }
+
+  /* find $HOME and add rest of path to config file: */
+  if (high_scores_file_path == NULL)
+    get_user_data_dir_with_subdir(opt_path);
+  else
+    strncpy(opt_path,high_scores_file_path,PATH_MAX);
+  strcat(opt_path, HIGHSCORE_FILENAME);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn write_high_scores() full path to file is: = %s\n", opt_path);
+  #endif
+
+  fp = fopen(opt_path, "a");
+  if (fp)
+  {
+    fprintf(fp,"%d\t%d\t%s\t\n",tableid,score,player_name);
+    fclose(fp);
+    fp = NULL;
+    return 1;
+  }
+  else
+    return 0;
+}
+  
+
+
+/* Checks to see if the current homedir has a menu_entries file, and if */
+/* so returns the names of the menu entries. This is used in cases      */
+/* where users must select their login information. Returns the number  */
+/* of menu entries (0 if there are none), and sets the input            */
+/* argument to a malloc-ed array of names (sets to NULL if there are no */
+/* choices to be made).  */
+int read_user_menu_entries(char ***user_names)
+{
+  FILE *fp;
+  int n_entries;
+  char opt_path[PATH_MAX],menu_entries_file[PATH_MAX];
+
+  // Look for a menu_entries file
+  get_user_data_dir_with_subdir(opt_path);
+  strncpy(menu_entries_file,opt_path,PATH_MAX);
+  strncat(menu_entries_file,USER_MENU_ENTRIES_FILENAME,PATH_MAX-strlen(menu_entries_file));
+  n_entries = 0;
+  fp = fopen(menu_entries_file,"r");
+  if (fp)
+  {
+    // There is a menu_entries file, read it
+    n_entries = read_lines_from_file(fp,user_names);
+    fclose(fp);
+  }
+
+  return n_entries;
+}
+
+/* Reads the user_login_questions file. The syntax is identical to
+   read_user_menu_entries. */
+int read_user_login_questions(char ***user_login_questions)
+{
+  FILE *fp;
+  int n_entries;
+  char opt_path[PATH_MAX],user_login_questions_file[PATH_MAX];
+
+  // Look for a user_login_questions file
+  get_user_data_dir_with_subdir(opt_path);
+  strncpy(user_login_questions_file,opt_path,PATH_MAX);
+  strncat(user_login_questions_file,USER_LOGIN_QUESTIONS_FILENAME,PATH_MAX-strlen(user_login_questions_file));
+  n_entries = 0;
+  fp = fopen(user_login_questions_file,"r");
+  if (fp)
+  {
+    // There is a user_login_questions file, read it
+    n_entries = read_lines_from_file(fp,user_login_questions);
+    fclose(fp);
+   }
+ 
+  return n_entries;
+}
+
+void user_data_dirname_up(void)
+{
+  dirname_up(user_data_dir);
+}
+
+void user_data_dirname_down(char *subdir)
+{
+  DIR *dir;
+
+  // The space for user_data_dir has to have sufficient memory
+  // available for concatenating subdir and a possible final "/",
+  // hence the +2s.
+  if (user_data_dir != NULL) {
+    user_data_dir = (char*) realloc(user_data_dir,(strlen(user_data_dir) + strlen(subdir) + 2)*sizeof(char));
+    if (user_data_dir == NULL) {
+      fprintf(stderr,"Error allocating memory in user_data_dirname_down.\n");
+      exit(EXIT_FAILURE);
+    }
+    strcat(user_data_dir,subdir);
+  }
+  else {
+    user_data_dir = (char*) malloc((strlen(subdir)+2)*sizeof(char));
+    if (user_data_dir == NULL) {
+      fprintf(stderr,"Error allocating memory in user_data_dirname_down.\n");
+      exit(EXIT_FAILURE);
+    }
+    strcpy(user_data_dir,subdir);
+  }
+  strcat(user_data_dir,"/");
+  dir = opendir(user_data_dir);
+  if (dir == NULL) {
+    printf("User data directory cannot be opened, there is a configuration error\n");
+    printf("Continuing anyway without saving or loading individual settings.\n");
+  }
+  else {
+    closedir(dir);
+    // If we have multi-user logins, don't create restrictive
+    // permissions on new or rewritten files
+    umask(0x0);
+  }
+}
+
+
+/***********************************************************
+*                                                          *
+*       "Private methods" with file scope only             *
+*                                                          *
+***********************************************************/
+
+
+/* This function does the heavy lifting, so to speak:     */
+/* Note that file_type simply indicates whether or not    */
+/* to change admin-only settings such as per_user_config. */
+/* FIXME return value only tells whether file pointer valid */
+int read_config_file(FILE *fp, int file_type)
+{
+  char buf[PATH_MAX];
+  char *parameter, *param_begin, *param_end, *value, *value_end;
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nEntering read_config_file()\n");
+  #endif
+
+  /* get out if file pointer invalid: */
+  if(!fp)
+  {
+    #ifdef TUXMATH_DEBUG
+    printf("config file pointer invalid!\n");
+    printf("Leaving read_config_file()\n");
+    #endif
+
+    fprintf(stderr, "config file pointer invalid!\n");
+    return 0;
+  }
+
+  /* make sure we start at beginning: */
+  rewind(fp);
+
+  /* read in a line at a time: */
+  while (fgets (buf, PATH_MAX, fp))
+  { 
+    #ifdef TUXMATH_DEBUG
+    //printf("Beginning fgets() loop\n");
+    #endif
+    /* "parameter" and "value" will contain the non-whitespace chars */
+    /* before and after the '=' sign, respectively.  e.g.:           */
+    /*                                                               */
+    /* fullscreen = 0;                                               */
+    /* parameter is "fullscreen"                                     */
+    /* value is '0'                                                  */
+    /*                                                               */
+
+    /* ignore comment lines */
+    if ((buf[0] == ';') || (buf[0] == '#'))
+    {
+      #ifdef TUXMATH_DEBUG
+      //printf("Skipping comment line\n");
+      #endif
+      continue;
+    }
+ 
+    /* First find parameter string and make a copy: */
+    /* start at beginning: */
+    param_begin = buf;
+    /* skip leading whitespace */
+    while (isspace(*param_begin))
+    {
+      ++param_begin;
+    }
+
+    /* If this was a blank line, then we don't have to process any more */
+    if (param_begin-buf >= strlen(buf))
+      continue;
+
+    /* now go from here to end of string, stopping at either */
+    /* whitespace or '=':   */
+    param_end = param_begin;
+    while (!isspace(*param_end)
+         && ('=' != (*param_end)))
+    {
+      ++param_end;
+    }
+
+    /* copy chars from start of non-whitespace up to '=': */
+    //parameter = strndup(param_begin, (param_end - param_begin));
+
+    /* Next three lines do same as strndup(), which may not be available: */
+    parameter = malloc((sizeof(char) * (param_end - param_begin)) + 1);
+    strncpy(parameter, param_begin, (param_end - param_begin));
+    parameter[param_end - param_begin] = '\0';
+ 
+   /* Now get value string: */
+    /* set value to first '=' in line: */
+    value = strchr(buf, '=');
+
+    if (!value || (value == buf))
+    {
+      #ifdef TUXMATH_DEBUG
+      //fprintf(stderr, "Error while reading prefs - line with no '='!\n");
+      #endif
+
+      free(parameter);
+      continue;
+    }
+
+    /* move past '=' sign: */
+    ++value;
+
+    /* skip leading whitespace */
+    while (isspace(*value))
+    { 
+      ++value;
+    }
+
+    value_end = value;
+
+    /* remove trailing whitespace or newline */
+    while (!isspace(*value_end)
+         && (0x0a != (*value_end))
+         && (*value_end))
+    {
+      ++value_end;
+    }
+    /* terminate string here: */
+    *value_end = 0;
+
+    tmdprintf("parameter = '%s'\t, length = %zu\n", parameter, strlen(parameter));
+    tmdprintf("value = '%s'\t, length = %zu\t, atoi() = %d\t, atof() = %.2f\n", value, strlen(value), atoi(value), atof(value));
+    
+    /* Now ready to handle each name/value pair! */
+    
+    /* Set general game_options struct (see tuxmath.h): */ 
+//    if(0 == strcasecmp(parameter, "per_user_config"))
+//    {
+//      /* Only let administrator change this setting */
+//      if (file_type == GLOBAL_CONFIG_FILE) 
+//      {
+//        int v = str_to_bool(value);
+//        if (v != -1)
+//          Opts_SetGlobalOpt(PER_USER_CONFIG, v);
+//      }
+//    }
+//                                 
+//    else if(0 == strcasecmp(parameter, "homedir"))
+//    {
+//      /* Only let administrator change this setting */
+//      if (file_type == GLOBAL_CONFIG_FILE && user_data_dir == NULL)
+//      {
+//        /* Check to see whether the specified homedir exists */
+//        dir = opendir(value);
+//        if (dir == NULL)
+//          fprintf(stderr,"homedir: %s is not a directory, or it could not be read\n", value);
+//        else {
+//          set_user_data_dir(value);  /* copy the homedir setting */
+//          closedir(dir);
+//        }
+//      }
+//    }
+//
+//    else if(0 == strcasecmp(parameter, "use_sound"))
+//    {
+//      int v = str_to_bool(value);
+//      if (v != -1)
+//        Opts_SetGlobalOpt(USE_SOUND, v);
+//    }
+//    else if(0 == strcasecmp(parameter, "menu_sound"))
+//    {
+//      int v = str_to_bool(value);
+//      if (v != -1)
+//        Opts_SetGlobalOpt(MENU_SOUND, v);
+//    }
+//
+//    else if(0 == strcasecmp(parameter, "menu_music"))
+//    {
+//      int v = str_to_bool(value);
+//      if (v != -1)
+//        Opts_SetGlobalOpt(MENU_MUSIC, v);
+//    }
+//
+//    else if(0 == strcasecmp(parameter, "fullscreen"))
+//    {
+//      int v = str_to_bool(value);
+//      if (v != -1)
+//        Opts_SetGlobalOpt(FULLSCREEN, v);
+//    }
+    //TODO herd these per-game options into their own "domain" as well
+    if(0 == strcasecmp(parameter, "use_bkgd"))
+    {
+      int v = str_to_bool(value);
+      if (v != -1)
+        Opts_SetUseBkgd(v);
+    }
+
+    else if(0 == strcasecmp(parameter, "demo_mode"))
+    {
+      int v = str_to_bool(value);
+      if (v != -1)
+        Opts_SetDemoMode(v);
+    }
+
+    else if(0 == strcasecmp(parameter, "oper_override"))
+    {
+      int v = str_to_bool(value);
+      if (v != -1)
+        Opts_SetOperOverride(v);
+    }
+
+    else if(0 == strcasecmp(parameter, "use_keypad"))
+    {
+      int v = str_to_bool(value);
+      if (v != -1)
+        Opts_SetGlobalOpt(USE_KEYPAD, v);
+    }
+
+    else if(0 == strcasecmp(parameter, "allow_pause"))
+    {
+      int v = str_to_bool(value);
+      if (v != -1)
+        Opts_SetAllowPause(v);
+    }
+
+    else if(0 == strcasecmp(parameter, "use_igloos"))
+    {
+      int v = str_to_bool(value);
+      if (v != -1)
+        Opts_SetGlobalOpt(USE_IGLOOS, v);
+    }
+
+    else if(0 == strcasecmp(parameter, "bonus_comet_interval"))
+    {
+      Opts_SetBonusCometInterval(atoi(value));
+    }
+
+    else if(0 == strcasecmp(parameter, "bonus_speed_ratio"))
+    {
+      Opts_SetBonusSpeedRatio(atof(value));
+    }
+
+    else if(0 == strcasecmp(parameter, "save_summary"))
+    {
+      int v = str_to_bool(value);
+      if (v != -1)
+        Opts_SetSaveSummary(v);
+    }
+
+    else if(0 == strcasecmp(parameter, "speed"))
+    {
+      Opts_SetSpeed(atof(value));
+    }
+
+    else if(0 == strcasecmp(parameter, "use_feedback"))
+    {
+      int v = str_to_bool(value);
+      if (v != -1)
+        Opts_SetUseFeedback(v);
+    }
+
+    else if(0 == strcasecmp(parameter, "danger_level"))
+    {
+      Opts_SetDangerLevel(atof(value));
+    }
+
+    else if(0 == strcasecmp(parameter, "danger_level_speedup"))
+    {
+      Opts_SetDangerLevelSpeedup(atof(value));
+    }
+
+    else if(0 == strcasecmp(parameter, "danger_level_max"))
+    {
+      Opts_SetDangerLevelMax(atof(value));
+    }
+
+    else if(0 == strcasecmp(parameter, "city_explode_handicap"))
+    {
+      Opts_SetCityExplHandicap(atof(value));
+    }
+
+    else if(0 == strcasecmp(parameter, "allow_speedup"))
+    {
+      int v = str_to_bool(value);
+      if (v != -1)
+        Opts_SetAllowSpeedup(v);
+    }
+
+    else if(0 == strcasecmp(parameter, "speedup_factor"))
+    {
+      Opts_SetSpeedupFactor(atof(value));
+    }
+
+    else if(0 == strcasecmp(parameter, "max_speed"))
+    {
+      Opts_SetMaxSpeed(atof(value));
+    }
+
+    else if(0 == strcasecmp(parameter, "slow_after_wrong"))
+    {
+      int v = str_to_bool(value);
+      if (v != -1)
+        Opts_SetSlowAfterWrong(v);
+    }
+
+    else if(0 == strcasecmp(parameter, "starting_comets"))
+    {
+      Opts_SetStartingComets(atoi(value));
+    }          
+
+    else if(0 == strcasecmp(parameter, "extra_comets_per_wave"))
+    {
+      Opts_SetExtraCometsPerWave(atoi(value));
+    }
+
+    else if(0 == strcasecmp(parameter, "max_comets"))
+    {
+      Opts_SetMaxComets(atoi(value));
+    }
+    
+    else if (0 == strcasecmp(parameter, "keep_score"))
+    {
+      Opts_SetKeepScore(atoi(value) );
+    }
+
+    else //we're going to delegate the setting of options to their subsystems
+    {
+      int ival = str_to_bool(value); //see if it's a valid bool
+      if (ival == -1) //guess not, must be an int
+        ival = atoi(value);
+      if (!parse_option(parameter, ival, file_type) )
+        printf("Sorry, I couldn't set %s\n", parameter);
+//        
+//      if (file_type != GLOBAL_CONFIG_FILE)
+//        MC_SetOp(parameter, ival); 
+//      else
+//      {
+//        if(0 != strcasecmp(parameter, "homedir"))
+//        {
+//          Opts_SetGlobalOp(parameter, ival);
+//        }
+//        else //set homedir
+//        {
+//          if (user_data_dir == NULL)
+//          {
+//            /* Check to see whether the specified homedir exists */
+//            dir = opendir(value);
+//            if (dir == NULL)
+//              fprintf(stderr,"homedir: %s is not a directory, or it could not be read\n", value);
+//            else {
+//              set_user_data_dir(value);  /* copy the homedir setting */
+//              closedir(dir);
+//            }
+//          }
+//        }
+//      }
+    }
+    free(parameter);
+  }
+  //handle min > max by disallowing operation
+  if (MC_GetOpt(MIN_AUGEND) > MC_GetOpt(MAX_AUGEND) || 
+      MC_GetOpt(MIN_ADDEND) > MC_GetOpt(MAX_ADDEND) )
+      MC_SetOpt(ADDITION_ALLOWED, 0);
+  if (MC_GetOpt(MIN_MINUEND) > MC_GetOpt(MAX_MINUEND) || 
+      MC_GetOpt(MIN_SUBTRAHEND) > MC_GetOpt(MAX_SUBTRAHEND) )
+      MC_SetOpt(SUBTRACTION_ALLOWED, 0);
+  if (MC_GetOpt(MIN_MULTIPLICAND) > MC_GetOpt(MAX_MULTIPLICAND) || 
+      MC_GetOpt(MIN_MULTIPLIER) > MC_GetOpt(MAX_MULTIPLIER) )
+      MC_SetOpt(MULTIPLICATION_ALLOWED, 0);
+  if (MC_GetOpt(MIN_DIVISOR) > MC_GetOpt(MAX_DIVISOR) || 
+      MC_GetOpt(MIN_QUOTIENT) > MC_GetOpt(MAX_QUOTIENT) )
+      MC_SetOpt(DIVISION_ALLOWED, 0);
+  if (MC_GetOpt(MIN_TYPING_NUM) > MC_GetOpt(MAX_TYPING_NUM) )
+      MC_SetOpt(TYPING_PRACTICE_ALLOWED, 0);
+      
+  #ifdef TUXMATH_DEBUG
+  printf("\nAfter file read in:\n");
+  write_config_file(stdout, 0);
+  printf("Leaving read_config_file()\n");
+  #endif
+
+  return 1;
+}
+
+/* determine which option class a name belongs to, and set it */
+/* accordingly. Returns 1 on success, 0 on failure            */
+static int parse_option(const char* name, int val, int file_type)
+{
+  int index = -1;
+  
+  if ((index = MC_MapTextToIndex(name)) != -1) //is it a math opt?
+  {
+    MC_SetOpt(index, val);
+  }
+  else if ((index = Opts_MapTextToIndex(name)) != -1) //is it a global opt?
+  {
+    if (file_type == GLOBAL_CONFIG_FILE)
+      Opts_SetGlobalOpt(index, val);
+  }
+  else //no? oh well.
+  {
+    return 0;
+  }
+  
+  return 1;
+}
+
+
+int write_user_config_file(void)
+{
+  char opt_path[PATH_MAX];
+  FILE* fp;
+
+  if (!find_tuxmath_dir())
+  {
+    fprintf(stderr, "\nCould not find or create tuxmath dir\n");
+    return 0;
+  }
+
+  /* find $HOME and add rest of path to config file: */
+  get_user_data_dir_with_subdir(opt_path);
+  strcat(opt_path, OPTIONS_FILENAME);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn write_user_config_file() full path to config file is: = %s\n", opt_path);
+  #endif
+
+  /* save settings: */
+  fp = fopen(opt_path, "w");
+  if (fp)
+  {
+    write_config_file(fp, 1);
+    fclose(fp);
+    fp = NULL;
+    return 1;
+  }
+  else
+    return 0;
+}
+
+
+
+/* this function writes the settings for all game options to a */
+/* human-readable file.                                        */
+int write_config_file(FILE *fp, int verbose)
+{
+  int i, vcommentsprimed = 0;
+  static char* vcomments[NOPTS]; //comments when writing out verbose
+  if (!vcommentsprimed) //we only want to initialize these once
+  {
+    vcommentsprimed = 1;
+    for (i = 0; i < NOPTS; ++i)
+      vcomments[i] = NULL;
+    vcomments[PLAY_THROUGH_LIST] =
+    "############################################################\n"
+    "#                                                          #\n"
+    "#              Tuxmath Configuration File                  #\n"
+    "#                                                          #\n"
+    "# The behavior of Tuxmath can be controlled to a great     #\n"
+    "# extent by editing this file with any and saving it in    #\n"
+    "# the default options location ($HOME/.tuxmath/options).   #\n"
+    "# The file consists of 'NAME = VALUE' pairs, one pair per  #\n"
+    "# line. Each option is one of the following types:         #\n"
+    "#                                                          #\n"
+    "#     boolean: 1 (synonyms 'true', 'T', 'yes', 'Y', 'on')  #\n"
+    "#              or                                          #\n"
+    "#              0 (synonyms 'false, 'F', 'no', 'N', 'off')  #\n"
+    "#     integer  (i.e. non-fractional numbers)               #\n"
+    "#     float    (i.e decimal fractions)                     #\n"
+    "#                                                          #\n"
+    "# Lines beginning with '#' or ';' are ignored as comments. #\n"
+    "# The synonyms for boolean '0' and '1' are accepted as     #\n"
+    "# input, but always written as '0' or '1' when Tuxmath     #\n"
+    "# writes a config file to disk.                            #\n"
+    "# The file is organized with the more important options    #\n"
+    "# first.                                                   #\n"
+    "############################################################\n"
+    "\n"
+    "############################################################\n"
+    "#                                                          #\n"
+    "#                       Game Mode                          #\n"
+    "#                                                          #\n"
+    "# Parameter: play_through_list (Boolean)                   #\n"
+    "# Default: 1                                               #\n"
+    "#                                                          #\n"
+    "# Tuxmath generates a list of math questions based on      #\n"
+    "# parameters set below.  By default, (play_through_list =  #\n"
+    "# 1) the questions are asked in a random order.            #\n"
+    "# Correctly answered questions are removed from the list.  #\n"
+    "# If the player fails to correctly answer a question       #\n"
+    "# before it hits a city, the question will be reinserted   #\n"
+    "# into the list in a random location.                      #\n"
+    "# The player wins if all questions are answered correctly  #\n"
+    "# before the cities are destroyed.                         #\n"
+    "#                                                          #\n"
+    "# Alternatively, Tuxmath can be played in 'Arcade Mode'    #\n"
+    "# by setting play_through_list = 0 (i.e. 'false'). If this #\n"
+    "# is done, all questions will be randomly reinserted into  #\n"
+    "# the list whether or not they are answered correctly, and #\n"
+    "# the game continues as long as there is a surviving city. #\n"
+    "############################################################\n"
+    "\n";                                                           
+    
+    vcomments[ADDITION_ALLOWED] = 
+    "\n############################################################\n"
+    "#                                                          #\n"
+    "#               Selecting Math Operations                  #\n"
+    "#                                                          #\n"
+    "# Parameter: addition_allowed (boolean)                    #\n"
+    "# Default: 1                                               #\n"
+    "# Parameter: subtraction_allowed (boolean)                 #\n"
+    "# Default: 1                                               #\n"
+    "# Parameter: multiplication_allowed (boolean)              #\n"
+    "# Default: 1                                               #\n"
+    "# Parameter: division_allowed (boolean)                    #\n"
+    "# Default: 1                                               #\n"
+    "#                                                          #\n"
+    "# These options enable questions for each of the four math #\n"
+    "# operations.  All are 1 (yes) by default.                 #\n"
+    "############################################################\n\n";
+    vcomments[TYPING_PRACTICE_ALLOWED] =
+    "\n############################################################\n"
+    "#                                                          #\n"
+    "#                    Typing Practice                       #\n"
+    "#                                                          #\n"
+    "# Parameter: typing_practice_allowed (boolean)             #\n"
+    "# Default: 0                                               #\n"
+    "#                                                          #\n"
+    "# This option simply displays numbers for the youngest     #\n"
+    "# players to type in to learn the keyboard.                #\n"
+    "############################################################\n\n";
+    vcomments[ALLOW_NEGATIVES] =
+    "\n############################################################\n"
+    "#                                                          #\n"
+    "#                 Negative Number Support                  #\n"
+    "#                                                          #\n"
+    "# Parameter: allow_negatives (boolean)                     #\n"
+    "# Default: 0                                               #\n"
+    "#                                                          #\n"
+    "# 'allow_negatives' allows or disallows use of negative    #\n"
+    "# numbers as both operands and answers.  Default is 0      #\n"
+    "# (no), which disallows questions like:                    #\n"
+    "#          2 - 4 = ?                                       #\n"
+    "# Note: this option must be enabled in order to set the    #\n"
+    "# operand ranges to include negatives. If it is changed    #\n"
+    "# from 1 (yes) to 0 (no), any negative operand limits will #\n"
+    "# be reset to 0.                                           #\n"
+    "############################################################\n\n";
+    vcomments[MIN_AUGEND] = 
+    "\n############################################################\n"
+    "#                                                          #\n"
+    "#      Minimum and Maximum Values for Operand Ranges       #\n"
+    "#                                                          #\n"
+    "# Parameters: (multiple - all integer type)                #\n"
+    "#                                                          #\n"
+    "# Operand limits can be set to any integer up to the       #\n"
+    "# value of 'max_answer'. Tuxmath will generate questions   #\n"
+    "# for every value in the specified range. The maximum must #\n"
+    "# be greater than or equal to the corresponding minimum    #\n"
+    "# for any questions to be generated for that operation.    #\n"
+    "# Defaults are 0 for minima and 12 for maxima.             #\n"
+    "#                                                          #\n"
+    "# Note: 'allow_negatives' must be set to 1 for negative    #\n"
+    "# values to be accepted (see 'Advanced Options').          #\n"
+    "############################################################\n"
+    "\n# Addition operands:\n"
+    "# augend + addend = sum\n\n";
+    vcomments[MIN_MINUEND] = 
+    "\n# Subtraction operands:\n"
+    "# minuend - subtrahend = difference\n\n";
+    vcomments[MIN_MULTIPLIER] = 
+    "\n# Multiplication operands:\n"
+    "# multiplier * multiplicand = product\n\n";
+    vcomments[MIN_DIVISOR] = 
+    "\n# Division operands:\n"
+    "# dividend / divisor = quotiend\n\n";
+    vcomments[MIN_TYPING_NUM] =
+    "\n# Typing practice:\n";
+    vcomments[QUESTION_COPIES] = 
+    "\n\n\n############################################################\n"
+    "#                                                          #\n"
+    "#                   Advanced Options                       #\n"
+    "#                                                          #\n"
+    "# The remaining settings further customize Tuxmath's       #\n"
+    "# behavior.  Most users will probably not change them.     #\n"
+    "############################################################\n\n"
+    
+    "\n############################################################\n"
+    "#                                                          #\n"
+    "#           Advanced Math Question List Options            #\n"
+    "#                                                          #\n"
+    "# Parameter: question_copies (integer)                     #\n"
+    "# Default: 1                                               #\n"
+    "# Parameter: repeat_wrongs (boolean)                       #\n"
+    "# Default: 1                                               #\n"
+    "# Parameter: copies_repeated_wrongs (integer)              #\n"
+    "# Default: 1                                               #\n"
+    "# Parameter: fraction_to_keep (float)                      #\n"
+    "# Default: 1                                               #\n"
+    "#                                                          #\n"
+    "# These settings offer further control over the question   #\n"
+    "# list and are generally only useful if 'play_through_list'#\n"
+    "# is enabled (as it is by default).                        #\n"
+    "#                                                          #\n"
+    "# 'question_copies' is the number of times each question   #\n"
+    "# is put into the initial list. It can be 1 to 10.         #\n"
+    "#                                                          #\n"
+    "# 'repeat_wrongs' determines whether questions the player  #\n"
+    "# failed to answer correctly will be asked again.          #\n"
+    "#                                                          #\n"
+    "# 'copies_repeated_wrongs' gives the number of times a     #\n"
+    "# missed question will reappear. This can be set anywhere  #\n"
+    "# from 1 to 10.                                            #\n"
+    "#                                                          #\n"
+    "# 'fraction_to_keep' allows a list to be generated that    #\n"
+    "# consists of a randomly-selected subset of the questions  #\n"
+    "# fitting the criteria.  The parameter is a float that     #\n"
+    "# must be greater than 0 and less than or equal to 1. For  #\n"
+    "# example, a value of 0.1 means 10%% of the questions      #\n"
+    "# meeting the criteria will go into the list.              #\n"
+    "#                                                          #\n"
+    "# The defaults for these values result in a 'mission'      #\n" 
+    "# for Tux that is accomplished by answering all            #\n"
+    "# questions correctly with at least one surviving city.    #\n"
+    "############################################################\n\n";
+    vcomments[FORMAT_ADD_ANSWER_LAST] =
+    "\n############################################################\n"
+    "#                                                          #\n"
+    "#                 Math Question Formats                    #\n"
+    "#                                                          #\n"
+    "# The 'format_<op>_answer_<place>  options control         #\n"
+    "# generation of questions with the answer in different     #\n"
+    "# places in the equation.  i.e.:                           #\n"
+    "#                                                          #\n"
+    "#    format_add_answer_last:    2 + 2 = ?                  #\n"
+    "#    format_add_answer_first:   ? + 2 = 4                  #\n"
+    "#    format_add_answer_middle:  2 + ? = 4                  #\n"
+    "#                                                          #\n"
+    "# By default, 'format_answer_first' is enabled and the     #\n"
+    "# other two formats are disabled.  Note that the options   #\n"
+    "# are not mutually exclusive - the question list may       #\n"
+    "# contain questions with different formats.                #\n"
+    "#                                                          #\n"
+    "# The formats are set independently for each of the four   #\n"
+    "# math operations. All parameters are type 'boolean'.      #\n"
+    "############################################################\n\n";
+    vcomments[MAX_ANSWER] = 
+    "\n############################################################\n"
+    "#                                                          #\n"
+    "# Parameter: max_answer (integer)                          #\n"
+    "# Default: 999                                             #\n"
+    "#                                                          #\n"
+    "# 'max_answer' is the largest absolute value allowed in    #\n"
+    "# any value in a question (not only the answer). Default   #\n"
+    "# is 999, which is as high as it can be set. It can be set #\n"
+    "# lower to fine-tune the list for certain 'lessons'.       #\n"
+    "############################################################\n\n";
+    vcomments[MAX_QUESTIONS] = 
+    "\n############################################################\n"
+    "#                                                          #\n"
+    "# Parameter: max_questions (integer)                       #\n"
+    "# Default: 5000                                            #\n"
+    "#                                                          #\n"
+    "# 'max_questions' is limit of the length of the question   #\n"
+    "# list. Default is 5000 - only severe taskmasters will     #\n"
+    "# need to raise it!                                        #\n"
+    "############################################################\n\n";
+    vcomments[RANDOMIZE] = 
+    "\n############################################################\n"
+    "#                                                          #\n"
+    "# Parameter: randomize (boolean)                           #\n"
+    "# Default: 1                                               #\n"
+    "#                                                          #\n"
+    "# If 'randomize' selected, the list will be shuffled       #\n"
+    "# at the start of the game. Otherwise, the questions       #\n"
+    "# appear in the order the program generates them.          #\n"
+    "############################################################\n\n";
+    
+  }
+  
+  tmdprintf("\nEntering write_config_file()\n");
+
+  /* get out if file pointer null */
+  if(!fp)
+  {
+    fprintf (stderr, "write_config_file() - file pointer invalid/n");
+    tmdprintf("Leaving write_config_file()\n");
+    return 0;
+  }
+  
+  for (i = 0; i < NOPTS; ++i) //for each option
+  {
+    if (verbose && vcomments[i]) //comment goes before
+      fprintf(fp, vcomments[i]);
+    fprintf(fp, "%s = %d\n", MC_OPTION_TEXT[i], MC_GetOpt(i) );
+  }
+  
+  if (verbose)
+  {
+    //allow_speedup comment
+  }
+  fprintf(fp, "allow_speedup = %d\n", Opts_AllowSpeedup() );
+  
+  if (verbose)
+  {
+    //use_sound comment
+  } 
+  fprintf(fp, "use_sound = %d\n", Opts_GetGlobalOpt(USE_SOUND) );
+  
+  if (verbose)
+  {
+    fprintf (fp, "\n############################################################\n" 
+                 "#                                                          #\n"
+                 "#                Advanced Comet Speed Options              #\n"
+                 "#                                                          #\n"
+                 "# Parameter: starting_comets (integer)                     #\n"
+                 "# Default: 2                                               #\n"
+                 "# Parameter: extra_comets_per_wave (integer)               #\n"
+                 "# Default: 2                                               #\n"
+                 "# Parameter: max_comets (integer)                          #\n"
+                 "# Default: 10                                              #\n"
+                 "# Parameter: speed (float)                                 #\n"
+                 "# Default: 1.00                                            #\n"
+                 "# Parameter: max_speed (float)                             #\n"
+                 "# Default: 10.00                                           #\n"
+                 "# Parameter: speedup_factor (float)                        #\n"
+                 "# Default: 1.20                                            #\n"
+                 "# Parameter: bonus_comet_interval (integer)                #\n"
+                 "# Default: 10                                              #\n"
+                 "# Parameter: bonus_speed_ratio (float)                     #\n"
+                 "# Default: 1.50                                            #\n"
+                 "# Parameter: slow_after_wrong (bool)                       #\n"
+                 "# Default: 0                                               #\n"
+                 "#                                                          #\n"
+                 "# (for 'feedback' speed control system):                   #\n"
+                 "# Parameter: danger_level (float)                          #\n"
+                 "# Default: 0.35                                            #\n"
+                 "# Parameter: danger_level_speedup (float)                  #\n"
+                 "# Default: 1.1                                             #\n"
+                 "# Parameter: danger_level_max (float)                      #\n"
+                 "# Default: 0.9                                             #\n"
+                 "# Parameter: city_explode_handicap (float)                 #\n"
+                 "# Default: 0                                               #\n"
+                 "#                                                          #\n"
+                 "# The comet number parameters and initial/max speed apply  #\n"
+                 "# whether or not the feedback system is activated.         #\n"
+                 "#                                                          #\n"
+                 "# 'speedup_factor' and 'slow_after_wrong' only apply if    #\n"
+                 "# feedback is not activated.                               #\n"
+                 "#                                                          #\n"
+                 "# The 'danger_level_*' and 'city_explode_handicap'         #\n"
+                 "# parameters are only used if feedback is activated.       #\n"
+                 "############################################################\n\n");
+  }
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Number of comets for first wave. Default is 2.\n");
+  }
+  fprintf(fp, "starting_comets = %d\n", Opts_StartingComets());
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Comets to add for each successive wave. Default is 2.\n");
+  }
+  fprintf(fp, "extra_comets_per_wave = %d\n", Opts_ExtraCometsPerWave());
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Maximum number of comets. Default is 10.\n");
+  }
+  fprintf(fp, "max_comets = %d\n", Opts_MaxComets());
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Starting comet speed. Default is 1.\n");
+  }
+  fprintf(fp, "speed = %.2f\n", Opts_Speed());
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Maximum speed. Default is 10.\n");
+  }
+  fprintf(fp, "max_speed = %.2f\n", Opts_MaxSpeed());
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# 'speedup_factor': If feedback is not used but \n"
+                 "# 'allow_speedup' is enabled, the comet speed will be\n"
+                 "# multiplied by this factor with each new wave.\n"
+                 "# Values from 0.5 to 2 are accepted (note that a \n"
+                 "# value less than 1 causes the comets to be \n"
+                 "# slower with each wave!).\n"
+                 "# Default is 1.2 (i.e. 20 percent increase per wave)\n\n");
+  }
+  fprintf(fp, "speedup_factor = %.2f\n", Opts_SpeedupFactor());
+
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# 'bonus_comet_interval' controls how frequently\n"
+                 "# special comets appear that cause a igloo to be  \n"
+                 "# rebuilt if answered correctly. The bonus comet  \n"
+                 "# appears after this number of regular comets (a  \n"
+                 "# value of 0 disables bonus comets). Default is 10. \n");
+  }
+  fprintf(fp, "bonus_comet_interval = %d\n", Opts_BonusCometInterval());
+
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# 'bonus_speed_ratio' determines how fast the\n"
+                 "# bonus comets fall relative to the regular comets.\n"
+                 "# Range 1.0 - 3.0, default 1.5:\n");
+  }
+  fprintf(fp, "bonus_speed_ratio = %.2f\n", Opts_BonusSpeedRatio());
+
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# 'slow_after_wrong' tells Tuxmath to go back to  \n"
+                 "# starting speed and number of comets if the player misses \n"
+                 "# a question. Useful for smaller kids. Default is 0.\n\n");
+  }
+
+  fprintf(fp, "slow_after_wrong = %d\n", Opts_SlowAfterWrong());
+
+
+  if(verbose)
+  {
+     fprintf (fp, "\n# (Feedback) Set the desired danger level.\n"
+             "# 0 = too safe, comets typically exploded at the very top\n"
+             "# 1 = too dangerous, comets typically exploded as they\n"
+             "# hit cities. Set it somewhere between these extremes. As\n"
+             "# a guideline, early elementary kids might prefer\n"
+             "# 0.2-0.3, older kids at around 0.4-0.6. Default 0.35.\n\n");
+  }
+  fprintf(fp, "danger_level = %.2f\n", Opts_DangerLevel());
+
+  if(verbose)
+  {
+     fprintf (fp, "\n# (Feedback) Set danger level speedup.\n"
+                  "# The margin of safety will decrease by this factor each\n"
+                  "# wave. Default 1.1. Note 1 = no increase in danger level.\n\n");
+  }
+  fprintf(fp, "danger_level_speedup = %.2f\n", Opts_DangerLevelSpeedup());
+
+  if(verbose)
+  {
+     fprintf (fp, "\n# (Feedback) Set the maximum danger level.\n"
+                  "# Default 0.9.\n");
+  }
+  fprintf(fp, "danger_level_max = %.2f\n", Opts_DangerLevelMax());
+
+  if (verbose)
+  { 
+     fprintf (fp, "\n# (Feedback) Set the handicap for hitting cities.\n"
+                  "# When bigger than 0, this causes the game to slow down\n"
+                  "# by an extra amount after a wave in which one or more\n"
+                  "# cities get hit. Note that this is similar to\n"
+                  "# 'slow_after_wrong', but allows for more gradual\n"
+                  "# changes. Default 0 (no extra handicap).\n\n");
+  }
+  fprintf(fp, "city_explode_handicap = %.2f\n", Opts_CityExplHandicap());
+
+  if(verbose)
+  {
+    fprintf (fp, "\n\n############################################################\n" 
+                 "#                                                          #\n"
+                 "#                  Managing User Settings                  #\n"
+                 "#                                                          #\n"
+                 "# Parameter: per_user_config (boolean)                     #\n"
+                 "# Default: 1                                               #\n"
+                 "# Parameter: homedir (string)                              #\n"
+                 "# Default: <none supplied>                                 #\n"
+                 "#                                                          #\n"
+                 "# 'per_user_config' determines whether Tuxmath will look   #\n"
+                 "# in the user's home directory for settings. Default is 1  #\n"
+                 "# (yes). If set to 0, the program will ignore the user's   #\n"
+                 "# .tuxmath file and use the the global settings in the     #\n"
+                 "# installation-wide config file.                           #\n"
+                 "#                                                          #\n"
+                 "# 'homedir' allows you to specify the location to look for #\n"
+                 "# user home directories. You probably do not want to       #\n"
+                 "# specify this unless all users share the same login       #\n"
+                 "# account. See the README for details on configuration.    #\n"
+                 "# To enable this feature, remove the '#' comment mark and  #\n"
+                 "# set the path as desired.                                 #\n"
+                 "#                                                          #\n"
+                 "# These settings cannot be changed by an ordinary user, as #\n"
+                 "# they are ignored unless the config file is Tuxmath's     #\n"
+                 "# global config file. Thus, users cannot 'lock themselves  #\n"
+                 "# out' by accidentally setting per_user_config to 0.       #\n"
+                 "############################################################\n\n");
+  }
+  fprintf(fp, "per_user_config = %d\n", Opts_GetGlobalOpt(PER_USER_CONFIG));
+  fprintf(fp, "# homedir = /servervolume/tuxmath_users\n");
+
+
+  /* print general game options (passing '1' as second arg causes */
+  /* "help" info for each option to be written to file as comments) */
+//  print_game_options(fp, 1);
+  /* print options pertaining to math questions from MathCards: */
+//  MC_PrintMathOptions(fp, 1);
+
+  #ifdef TUXMATH_DEBUG
+  printf("Leaving write_config_file()\n");
+  #endif
+
+  return 1;
+}
+
+
+/* write_pregame_summary() and write_postgame_summary() are used to */
+/* record data about the player's game to file for review (perhaps by */
+/* teacher). write_pregame_summary() is called at the start of each  */
+/* game and records the question list along with identifying data. It */
+/* also rotates old game summaries to successive filenames, keeping   */
+/* the last ten summaries for review. write_postgame_summary()       */
+/* the list of questions that were not answered correctly and         */
+/* calculates the percent correct.                                    */
+int write_pregame_summary(void)
+{
+  int i;
+  FILE* fp;
+  char filepath1[PATH_MAX];
+  char filepath2[PATH_MAX];
+
+  /* Make sure tuxmath dir exists or can be created: */
+  if (!find_tuxmath_dir())
+  {
+    fprintf(stderr, "\nCould not find or create tuxmath dir\n");
+    return 0;
+  }
+
+
+
+  /* Rotate filenames of old summaries, oldest summary if present */
+  /* and leaving summary1 available for current game:             */
+
+  /* find $HOME and tack on file name: */
+  get_user_data_dir_with_subdir(filepath1);
+  strcat(filepath1, summary_filenames[NUM_SUMMARIES - 1]);
+
+  fp = fopen(filepath1, "r");
+  if (fp)
+  {
+    #ifdef TUXMATH_DEBUG
+    printf("\nIn write_pregame_summary() - removing oldest summary file\n");
+    #endif
+
+    fclose(fp);
+    remove(filepath1);
+  }
+
+  /* Now shift each old file back by one:       */
+  /* 'filepath1' is the old name for each file, */
+  /* 'filepath2' is the new name (i.e. we go from i - 1 to i). */
+  for (i = NUM_SUMMARIES - 1; i > 0; i--)
+  {
+    /* old filename: */
+    get_user_data_dir_with_subdir(filepath1);
+    strcpy(filepath2,filepath1);
+    strcat(filepath1, summary_filenames[i - 1]);
+    /* new filename: */
+    strcat(filepath2, summary_filenames[i]);
+    /* now change the name: */
+    rename(filepath1, filepath2);
+  } 
+
+  /* summary_filenames[0] (i.e. 'summary1') should now be vacant:     */
+  get_user_data_dir_with_subdir(filepath1);
+  strcat(filepath1, summary_filenames[0]);
+
+  fp = fopen(filepath1, "w"); /* "w" means start writing with empty file */
+  if (fp)
+  {
+    /* Write header and identifying data for summary file:       */
+    fprintf(fp, "************************\n"
+                "* Tuxmath Game Summary *\n"
+                "************************\n");
+    if (add_subdir)
+    {
+      /* Identify user by login if we're not in a multiuser configuration */
+      fprintf(fp, "\nPlayer: %s\n", getenv("USER"));
+    }
+    else {
+      /* Identify user by the directory name.*/
+      fprintf(fp, "\nPlayer: %s\n", get_user_name());
+    }
+
+    fprintf(fp, "\nMission: %s\n", last_config_file_name);
+
+    /* Write question list:  */
+    fprintf(fp, "\nStarting Question List:");
+    MC_PrintQuestionList(fp);
+    fprintf(fp, "\n\nNumber of Questions: %d", MC_StartingListLength());
+
+    fclose(fp);
+    return 1;
+  }
+  else /* Couldn't write file for some reason: */
+  {
+    return 0;
+  }
+}
+
+int write_postgame_summary(void)
+{
+  FILE *fp;
+  char filepath1[PATH_MAX];
+  int total_answered;
+  float median_time;
+  int success = 1;
+  int write_column_names = 0;
+  time_t filetime;
+  struct stat filestat;
+  struct tm datetime;
+  char *mission_name;
+
+  get_user_data_dir_with_subdir(filepath1);
+  strcat(filepath1, summary_filenames[0]);
+
+  total_answered = MC_NumAnsweredCorrectly() + MC_NumNotAnsweredCorrectly();
+  median_time = MC_MedianTimePerQuestion();
+
+  fp = fopen(filepath1, "a"); /* "a" means append to end of file */
+  if (fp)
+  {
+    /* Write list of questions missed: */
+    fprintf(fp, "\n\n\nList Of Questions Not Answered Correctly:");
+                MC_PrintWrongList(fp);
+    fprintf(fp, "\n\nNumber Of Distinct Questions Not Answered Correctly: %d",
+                MC_WrongListLength());
+
+    /* Write post-game statistics:     */
+
+    fprintf(fp, "\n\nSummary:\n");
+    fprintf(fp, "Questions Answered:\t%d\n", total_answered);
+    fprintf(fp, "Questions Correct:\t%d\n",
+                MC_NumAnsweredCorrectly());
+    fprintf(fp, "Questions Missed:\t%d\n",
+                MC_NumNotAnsweredCorrectly());
+    /* Avoid divide-by-zero errror: */
+    if (total_answered)
+    {
+      fprintf(fp, "Percent Correct:\t%d %%\n", 
+              ((MC_NumAnsweredCorrectly() * 100)/ total_answered) );
+    }
+    else
+      fprintf(fp, "Percent Correct: (not applicable)\n");
+
+    fprintf(fp,"Median Time/Question:\t%g\n",median_time);
+
+    fprintf(fp, "Mission Accomplished:\t");
+    if (MC_MissionAccomplished())
+    {
+      fprintf(fp, "Yes!\n\n8^)\n");
+    }
+    else
+    {
+      fprintf(fp, "No.\n\n:^(\n");
+    }
+
+    fclose(fp);
+  }
+  else /* Couldn't write file for some reason: */
+  {
+    fprintf(stderr,"Summary not written.\n");
+    success = 0;
+  }
+
+  /* Append brief summary to log */
+  if (total_answered > 0) {
+    /* We're going to want to write the date.  Use the filetime  */
+    /* rather than calling "time" directly, because "time"       */
+    /* returns the time according to whatever computer is        */
+    /* running tuxmath, and in a server/client mode it's likely  */
+    /* that some of the clients' clocks may be wrong. Use      */
+    /* instead the time according to the server on which the     */
+    /* accounts are stored, which can be extracted from the      */
+    /* modification time of the summary we just wrote.           */
+    if (stat(filepath1,&filestat) == 0) {
+      filetime = filestat.st_mtime;
+    } else {
+      filetime = time(NULL);
+    }
+    localtime_r(&filetime,&datetime); /* generate broken-down time */
+
+    get_user_data_dir_with_subdir(filepath1);
+    strcat(filepath1, "log.csv");
+    /* See whether the log file already exists; if not, write */
+    /* the column names */
+    fp = fopen(filepath1, "r");
+    if (fp == NULL)
+      write_column_names = 1;
+    else
+      fclose(fp);
+    
+    fp = fopen(filepath1, "a"); /* "a" means append to end of file */
+    if (fp) {
+      if (write_column_names) {
+        fprintf(fp,"\"User\",\"Mission\",\"Date\",\"Completed?\",\"Number answered\",\"Percent correct\",\"Time per question\"\n");
+      }
+      if (last_config_file_name)
+        mission_name = strdup(last_config_file_name);
+      else
+        mission_name = strdup("[NONE]");
+      fprintf(fp,"\"%s\",\"%s\",%d/%d/%d,%d,%d,%d,%g\n", get_user_name(), get_file_name(mission_name), datetime.tm_year+1900, datetime.tm_mon+1, datetime.tm_mday, MC_MissionAccomplished(), total_answered, ((MC_NumAnsweredCorrectly() * 100)/ total_answered), median_time);
+      fclose(fp);
+      free(mission_name);
+    } else
+      success = 0;
+  }
+  return success;
+}
+
+
+
+/* Checks to see if user's .tuxmath directory exists and, if not, tries  */
+/* to create it. Returns 1 if .tuxmath dir found or successfully created */
+static int find_tuxmath_dir(void)
+{
+  char opt_path[PATH_MAX];
+  DIR* dir_ptr;
+
+  /* find $HOME */
+  get_user_data_dir_with_subdir(opt_path);
+
+  #ifdef TUXMATH_DEBUG
+  printf("\nIn find_tuxmath_dir() tuxmath dir is: = %s\n", opt_path);
+  #endif
+
+  /* find out if directory exists - if not, create it: */
+  dir_ptr = opendir(opt_path);
+  if (dir_ptr)  /* don't leave DIR* open if it was already there */
+  {
+    #ifdef TUXMATH_DEBUG
+    printf("\nIn find_tuxmath_dir() tuxmath dir opened OK\n");
+    #endif
+
+    closedir(dir_ptr);
+    return 1;
+  }
+  else /* need to create tuxmath config directory: */
+  {
+    FILE* fp;
+    int status;
+
+    if (!add_subdir)
+      return 0;      // fail if the user specified a directory, but it doesn't exist
+
+    /* if user's home has a _file_ named .tuxmath (as from previous version */
+    /* of program), need to get rid of it or directory creation will fail:  */
+    fp = fopen(opt_path, "r");
+    if (fp)
+    {
+      #ifdef TUXMATH_DEBUG
+      printf("\nIn find_tuxmath_dir() - removing old .tuxmath file\n");
+      #endif
+
+      fclose(fp);
+      remove(opt_path);
+    }
+
+    #ifdef TUXMATH_DEBUG
+    printf("\nIn find_tuxmath_dir() - trying to create .tuxmath dir\n");
+    #endif
+
+    //status = mkdir(opt_path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+
+#ifndef BUILD_MINGW32
+    status = mkdir(opt_path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+#else
+    status = mkdir(opt_path);
+#endif
+
+    #ifdef TUXMATH_DEBUG
+    printf("\nIn find_tuxmath_dir() - mkdir returned: %d\n", status);
+    #endif
+
+    /* mkdir () returns 0 if successful */
+    if (0 == status)
+    {
+      fprintf(stderr, "\nfind_tuxmath_dir() - $HOME" OPTIONS_SUBDIR " created\n");
+      return 1;
+    }
+    else
+    {
+      fprintf(stderr, "\nfind_tuxmath_dir() - mkdir failed\n");
+      return 0;
+    }
+  }
+}
+
+
+
+/* A utility function to read lines from a textfile.  Upon exit, it */
+/* returns the # of lines successfully read, and sets the pointer   */
+/* array so that (*lines)[i] is a pointer to the text on the ith    */
+/* line.  Note this function also cleans up trailing whitespace,    */
+/* and skips blank lines.                                           */
+/* On entry, *lines must be NULL, as a sign that any previously     */
+/* allocated memory has been freed.                                 */
+static int read_lines_from_file(FILE *fp,char ***lines)
+{
+  char *fgets_return_val;
+  char name_buf[NAME_BUF_SIZE];
+  int n_entries;
+  int length;
+
+  n_entries = 0;
+  if(*lines != NULL) {
+    printf("Error: lines buffer was not NULL upon entry");
+    exit(EXIT_FAILURE);
+  }
+
+  fgets_return_val = fgets(name_buf,NAME_BUF_SIZE,fp);
+  while (fgets_return_val != NULL) {
+    // Strip terminal whitespace and \r
+    length = strlen(name_buf);
+    while (length>0 && (name_buf[length - 1] == '\r' || name_buf[length - 1] == '\n'|| name_buf[length-1] == ' ' || name_buf[length-1] == '\t')) {
+      name_buf[length - 1] = '\0';
+      length--;
+    }
+    if (length == 0) {
+      // If we get to a blank line, skip over it
+      fgets_return_val = fgets(name_buf,NAME_BUF_SIZE,fp);
+      continue;
+    }
+    n_entries++;
+    *lines = (char**) realloc(*lines,n_entries*sizeof(char*));
+    if (*lines == NULL) {
+      // Memory allocation error
+      printf("Error #1 allocating memory in read_lines_from_file\n");
+      exit(EXIT_FAILURE);
+    }
+    // Copy the cleaned-up line to the list
+    (*lines)[n_entries-1] = strdup(name_buf);
+    if ((*lines)[n_entries-1] == NULL) {
+      // Memory allocation error
+      printf("Error #2 allocating memory in read_lines_from_file\n");
+      exit(EXIT_FAILURE);
+    }
+    // Read the next line
+    fgets_return_val = fgets(name_buf,NAME_BUF_SIZE,fp);
+  }
+  return n_entries;
+}
+
+/* A utility function to go up one level in a directory hierarchy */
+static void dirname_up(char *dirname)
+{
+  int len;
+
+  len = strlen(dirname);
+  // Pass over all trailing "/"
+  while (len > 0 && dirname[len-1] == '/')
+    len--;
+
+  // Now pass over all non-"/" characters at the end
+  while (len > 0 && dirname[len-1] != '/')
+    len--;
+  
+  // Terminate the string after that next-to-last "/"
+  dirname[len] = '\0';
+}
+
+/* Identify user by the directory name. We don't want to use the */
+/* whole path, just the name of the last subdirectory. */
+static char* get_user_name(void)
+{
+  char filepath2[PATH_MAX];
+
+  get_user_data_dir_with_subdir(filepath2);
+  return get_file_name(filepath2);
+}
+
+/* Extract the last "field" in a full pathname */
+static char* get_file_name(char *fullpath)
+{
+  char *file_name;
+
+  file_name = &fullpath[strlen(fullpath)-1];
+  /* Chop off trailing "/" */
+  while (file_name > &fullpath[0] && *file_name == '/') {
+    *file_name = '\0';
+    file_name--;
+  }
+  /* Back up to the next "/" */
+  while (file_name > &fullpath[0] && *file_name != '/')
+    file_name--;
+
+  return ++file_name;
+}
+
+
+/* Allows use of "true", "YES", T, etc. in text file for boolean values. */
+/* Return value of -1 means value string is not recognized.              */
+/* Now reject non-"boolish" ints to prevent int/bool ambiguity           */
+static int str_to_bool(const char* val)
+{
+  char* ptr;
+
+  /* Check for recognized boolean strings: */
+  if ((0 == strcasecmp(val, "true"))
+    ||(0 == strcasecmp(val, "t"))
+    ||(0 == strcasecmp(val, "yes"))
+    ||(0 == strcasecmp(val, "y"))
+    ||(0 == strcasecmp(val, "1"))
+    ||(0 == strcasecmp(val, "on")))
+  {
+    return 1;
+  }
+
+  if ((0 == strcasecmp(val, "false"))
+    ||(0 == strcasecmp(val, "f"))
+    ||(0 == strcasecmp(val, "no"))
+    ||(0 == strcasecmp(val, "n"))
+    ||(0 == strcasecmp(val, "0"))
+  ||(0 == strcasecmp(val, "off")))
+  {
+    return 0;
+  }  
+
+  return -1;
+  
+  /* Return -1 if any chars are non-digits: */
+  ptr = (char*)val;
+  while (*ptr)
+  {
+    if (!isdigit(*ptr))
+      return -1;
+    ptr++;
+  }
+
+  /* If we get to here, val should be an integer. */
+  
+  if (atoi(val))
+    return 1;
+  else
+    return 0;
+}
+
+
+
+
+
+
+

Added: tuxmath/trunk/src/fileops.h
===================================================================
--- tuxmath/trunk/src/fileops.h	                        (rev 0)
+++ tuxmath/trunk/src/fileops.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,272 @@
+/*
+C Interface: fileops.h
+
+Description: File operations - together, fileops.h and fileops.c contain
+all code involving disk operations. The older header files images.h and
+sounds.h have been incorporated here. The intention is to make it easier to
+port tuxmath to other operating systems, as code to read and write as
+well as paths and file locations may be more OS-dependent.
+
+
+Author: David Bruce <dbruce at tampabay.rr.com>, (C) 2006
+Contains code originally written by Bill Kendrick (C) 2001.
+Copyright: See COPYING file that comes with this distribution (briefly, GNU GPL)
+*/
+
+#ifndef FILEOPS_H
+#define FILEOPS_H
+
+#include "globals.h"
+
+/* Flag basically telling whether or not to allow admin-level */
+/* settings to be changed: */
+enum {
+  USER_CONFIG_FILE,
+  GLOBAL_CONFIG_FILE
+};
+
+/* Names for images (formerly in images.h) */
+enum {
+  IMG_STANDBY,
+  IMG_MENU_BKG,
+  IMG_MENU_TITLE,
+//  IMG_LOADING,
+  IMG_TITLE,
+  IMG_LEFT,
+  IMG_LEFT_GRAY,
+  IMG_RIGHT,
+  IMG_RIGHT_GRAY,
+//  IMG_OPTIONS,
+  IMG_TUX4KIDS,
+  IMG_NBS,
+//  IMG_TUX_HELMET1,
+//  IMG_TUX_HELMET2,
+//  IMG_TUX_HELMET3,
+//  IMG_PLAY,
+//  IMG_CMD_OPTIONS,
+//  IMG_CMD_CREDITS,
+//  IMG_CMD_QUIT,
+/*  IMG_OPT_ADDITION,
+  IMG_OPT_SUBTRACTION,
+  IMG_OPT_MULTIPLICATION,
+  IMG_OPT_DIVISION,
+  IMG_OPT_MAX_ANSWER,
+  IMG_OPT_SPEED,
+  IMG_OPT_Q_RANGE,
+  IMG_OPT_RNG_1_5,
+  IMG_OPT_RNG_1_5_ON,
+  IMG_OPT_RNG_6_12,
+  IMG_OPT_RNG_6_12_ON,
+  IMG_OPT_RNG_13_20,
+  IMG_OPT_RNG_13_20_ON,
+  IMG_OPT_CHECK,
+  IMG_OPT_CHECK_ON,*/
+  IMG_CITY_BLUE,
+  IMG_CITY_BLUE_EXPL1,
+  IMG_CITY_BLUE_EXPL2,
+  IMG_CITY_BLUE_EXPL3,
+  IMG_CITY_BLUE_EXPL4,
+  IMG_CITY_BLUE_EXPL5,
+  IMG_CITY_BLUE_DEAD,
+  IMG_CITY_GREEN,
+  IMG_CITY_GREEN_EXPL1,
+  IMG_CITY_GREEN_EXPL2,
+  IMG_CITY_GREEN_EXPL3,
+  IMG_CITY_GREEN_EXPL4,
+  IMG_CITY_GREEN_EXPL5,
+  IMG_CITY_GREEN_DEAD,
+  IMG_CITY_ORANGE,
+  IMG_CITY_ORANGE_EXPL1,
+  IMG_CITY_ORANGE_EXPL2,
+  IMG_CITY_ORANGE_EXPL3,
+  IMG_CITY_ORANGE_EXPL4,
+  IMG_CITY_ORANGE_EXPL5,
+  IMG_CITY_ORANGE_DEAD,
+  IMG_CITY_RED,
+  IMG_CITY_RED_EXPL1,
+  IMG_CITY_RED_EXPL2,
+  IMG_CITY_RED_EXPL3,
+  IMG_CITY_RED_EXPL4,
+  IMG_CITY_RED_EXPL5,
+  IMG_CITY_RED_DEAD,
+  IMG_SHIELDS,
+  IMG_COMET1,
+  IMG_COMET2,
+  IMG_COMET3,
+  IMG_COMETEX8,
+  COMET_EXPL_END = IMG_COMETEX8,
+  IMG_COMETEX7,
+  IMG_COMETEX6,
+  IMG_COMETEX5,
+  IMG_COMETEX4,
+  IMG_COMETEX3,
+  IMG_COMETEX2,
+  IMG_COMETEX1,
+  COMET_EXPL_START = IMG_COMETEX1,
+  IMG_MINI_COMET1,
+  IMG_MINI_COMET2,
+  IMG_MINI_COMET3,
+  IMG_BONUS_COMET1,
+  IMG_BONUS_COMET2,
+  IMG_BONUS_COMET3,
+  IMG_BONUS_COMETEX8,
+  BONUS_COMET_EXPL_END = IMG_BONUS_COMETEX8,
+  IMG_BONUS_COMETEX7,
+  IMG_BONUS_COMETEX6,
+  IMG_BONUS_COMETEX5,
+  IMG_BONUS_COMETEX4,
+  IMG_BONUS_COMETEX3,
+  IMG_BONUS_COMETEX2,
+  IMG_BONUS_COMETEX1,
+  BONUS_COMET_EXPL_START = IMG_BONUS_COMETEX1,
+  IMG_NUMS,
+  IMG_LEDNUMS,
+  IMG_LED_NEG_SIGN,
+  IMG_PAUSED,
+  IMG_DEMO,
+  IMG_DEMO_SMALL,
+  IMG_KEYPAD,
+  IMG_KEYPAD_NO_NEG,
+//  IMG_CONSOLE,
+  IMG_CONSOLE_LED,
+  IMG_CONSOLE_BASH,
+  IMG_TUX_CONSOLE1,
+  IMG_TUX_CONSOLE2,
+  IMG_TUX_CONSOLE3,
+  IMG_TUX_CONSOLE4,
+  IMG_TUX_RELAX1,
+  IMG_TUX_RELAX2,
+  IMG_TUX_EGYPT1,
+  IMG_TUX_EGYPT2,
+  IMG_TUX_EGYPT3,
+  IMG_TUX_EGYPT4,
+  IMG_TUX_DRAT,
+  IMG_TUX_YIPE,
+  IMG_TUX_YAY1,
+  IMG_TUX_YAY2,
+  IMG_TUX_YES1,
+  IMG_TUX_YES2,
+  IMG_TUX_SIT,
+  IMG_TUX_FIST1,
+  IMG_TUX_FIST2,
+  IMG_PENGUIN_FLAPDOWN,
+  IMG_PENGUIN_FLAPUP,
+  IMG_PENGUIN_INCOMING,
+  IMG_PENGUIN_GRUMPY,
+  IMG_PENGUIN_WORRIED,
+  IMG_PENGUIN_STANDING_UP,
+  IMG_PENGUIN_SITTING_DOWN,
+  IMG_PENGUIN_WALK_ON1,
+  IMG_PENGUIN_WALK_ON2,
+  IMG_PENGUIN_WALK_ON3,
+  IMG_PENGUIN_WALK_OFF1,
+  IMG_PENGUIN_WALK_OFF2,
+  IMG_PENGUIN_WALK_OFF3,
+  IMG_IGLOO_MELTED3,
+  IMG_IGLOO_MELTED2,
+  IMG_IGLOO_MELTED1,
+  IMG_IGLOO_HALF,
+  IMG_IGLOO_INTACT,
+  IMG_IGLOO_REBUILDING1,
+  IMG_IGLOO_REBUILDING2,
+  IMG_STEAM1,
+  IMG_STEAM2,
+  IMG_STEAM3,
+  IMG_STEAM4,
+  IMG_STEAM5,
+  IMG_CLOUD,
+  IMG_SNOW1,
+  IMG_SNOW2,
+  IMG_SNOW3,
+  IMG_EXTRA_LIFE,
+  IMG_WAVE,
+  IMG_SCORE,
+  IMG_STOP,
+  IMG_NUMBERS,
+  IMG_GAMEOVER,
+  IMG_GAMEOVER_WON,
+  BG_STARS,
+  IMG_ASTEROID1,
+  IMG_ASTEROID2,
+  IMG_ASTEROID3,
+  IMG_SHIP01,
+  IMG_FACTOROIDS,
+  IMG_FACTORS,
+  IMG_TUX_LITTLE,
+  IMG_GOOD,
+  NUM_IMAGES
+};
+
+/* Names for game sounds (formerly in sounds.h): */
+enum {
+  SND_HARP,
+  SND_POP,
+  SND_TOCK,
+  SND_LASER,
+  SND_BUZZ,
+  SND_ALARM,
+  SND_SHIELDSDOWN,
+  SND_EXPLOSION,
+  SND_CLICK,
+  SND_SIZZLE,
+  SND_BONUS_COMET,
+  SND_EXTRA_LIFE,
+  NUM_SOUNDS
+};
+
+/* Names for background music (also formerly in sounds.h): */
+enum {
+  MUS_GAME,
+  MUS_GAME2,
+  MUS_GAME3,
+  NUM_MUSICS
+};
+
+/* Names for game summary files: */
+enum {
+  SUMMARY1,
+  SUMMARY2,
+  SUMMARY3,
+  SUMMARY4,
+  SUMMARY5,
+  SUMMARY6,
+  SUMMARY7,
+  SUMMARY8,
+  SUMMARY9,
+  SUMMARY10,
+  NUM_SUMMARIES
+};
+
+/* These functions used by setup() and titlescreen() to read in settings: */
+int read_global_config_file(void);
+int read_user_config_file(void);
+int parse_lesson_file_directory(void);
+int read_named_config_file(const char* fn);
+int write_user_config_file(void);
+int read_high_scores(void);
+int append_high_score(int tableid,int score,char *player_name);
+void set_high_score_path(void);
+void set_user_data_dir(const char* dirname);
+int write_goldstars(void);
+
+/* These functions are used by titlescreen() to assist with the login */
+int read_user_menu_entries(char ***user_names);
+int read_user_login_questions(char ***user_login_questions);
+int high_scores_found_in_user_dir(void);
+void set_high_score_path(void);
+void user_data_dirname_up(void);
+void user_data_dirname_down(char *subdir);
+
+/* These functions used by game() to record game summary: */
+int write_pregame_summary(void);
+int write_postgame_summary(void);
+
+int load_image_data();
+int load_default_font();
+
+
+#ifndef NOSOUND
+int load_sound_data();
+#endif
+
+#endif

Added: tuxmath/trunk/src/fileops_media.c
===================================================================
--- tuxmath/trunk/src/fileops_media.c	                        (rev 0)
+++ tuxmath/trunk/src/fileops_media.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,292 @@
+#include "tuxmath.h"
+#include "fileops.h"
+#include "loaders.h"
+#include "options.h"
+#include "SDL_extras.h"
+
+TTF_Font  *default_font;
+TTF_Font  *help_font;
+int glyph_offset;
+
+/*****************************************************************/
+/*   Loading of data files for images and sounds.                */
+/*   These functions also draw some user feedback to             */
+/*   display the progress of the loading.                        */
+/*****************************************************************/
+
+/* returns 1 if all data files successfully loaded, 0 otherwise. */
+
+/* TODO load only "igloo" or "city" files, not both.             */
+/* TODO get rid of files no longer used.                         */
+
+int load_image_data()
+{
+  int i;
+
+  static char* image_filenames[NUM_IMAGES] = {
+  DATA_PREFIX "/images/status/standby.png",
+  DATA_PREFIX "/images/title/menu_bkg.jpg",
+  DATA_PREFIX "/images/title/title1.png",
+  DATA_PREFIX "/images/status/title.png",
+  DATA_PREFIX "/images/status/left.png",
+  DATA_PREFIX "/images/status/left_gray.png",
+  DATA_PREFIX "/images/status/right.png",
+  DATA_PREFIX "/images/status/right_gray.png",
+  DATA_PREFIX "/images/status/tux4kids.png",
+  DATA_PREFIX "/images/status/nbs.png",
+  DATA_PREFIX "/images/cities/city-blue.png",
+  DATA_PREFIX "/images/cities/csplode-blue-1.png",
+  DATA_PREFIX "/images/cities/csplode-blue-2.png",
+  DATA_PREFIX "/images/cities/csplode-blue-3.png",
+  DATA_PREFIX "/images/cities/csplode-blue-4.png",
+  DATA_PREFIX "/images/cities/csplode-blue-5.png",
+  DATA_PREFIX "/images/cities/cdead-blue.png",
+  DATA_PREFIX "/images/cities/city-green.png",
+  DATA_PREFIX "/images/cities/csplode-green-1.png",
+  DATA_PREFIX "/images/cities/csplode-green-2.png",
+  DATA_PREFIX "/images/cities/csplode-green-3.png",
+  DATA_PREFIX "/images/cities/csplode-green-4.png",
+  DATA_PREFIX "/images/cities/csplode-green-5.png",
+  DATA_PREFIX "/images/cities/cdead-green.png",
+  DATA_PREFIX "/images/cities/city-orange.png",
+  DATA_PREFIX "/images/cities/csplode-orange-1.png",
+  DATA_PREFIX "/images/cities/csplode-orange-2.png",
+  DATA_PREFIX "/images/cities/csplode-orange-3.png",
+  DATA_PREFIX "/images/cities/csplode-orange-4.png",
+  DATA_PREFIX "/images/cities/csplode-orange-5.png",
+  DATA_PREFIX "/images/cities/cdead-orange.png",
+  DATA_PREFIX "/images/cities/city-red.png",
+  DATA_PREFIX "/images/cities/csplode-red-1.png",
+  DATA_PREFIX "/images/cities/csplode-red-2.png",
+  DATA_PREFIX "/images/cities/csplode-red-3.png",
+  DATA_PREFIX "/images/cities/csplode-red-4.png",
+  DATA_PREFIX "/images/cities/csplode-red-5.png",
+  DATA_PREFIX "/images/cities/cdead-red.png",
+  DATA_PREFIX "/images/cities/shields.png",
+  DATA_PREFIX "/images/comets/comet1.png",
+  DATA_PREFIX "/images/comets/comet2.png",
+  DATA_PREFIX "/images/comets/comet3.png",
+  DATA_PREFIX "/images/comets/cometex3.png",
+  DATA_PREFIX "/images/comets/cometex3.png",
+  DATA_PREFIX "/images/comets/cometex2.png",
+  DATA_PREFIX "/images/comets/cometex2.png",
+  DATA_PREFIX "/images/comets/cometex1a.png",
+  DATA_PREFIX "/images/comets/cometex1a.png",
+  DATA_PREFIX "/images/comets/cometex1.png",
+  DATA_PREFIX "/images/comets/cometex1.png",
+  DATA_PREFIX "/images/comets/mini_comet1.png",
+  DATA_PREFIX "/images/comets/mini_comet2.png",
+  DATA_PREFIX "/images/comets/mini_comet3.png",
+  DATA_PREFIX "/images/comets/bonus_comet1.png",
+  DATA_PREFIX "/images/comets/bonus_comet2.png",
+  DATA_PREFIX "/images/comets/bonus_comet3.png",
+  DATA_PREFIX "/images/comets/bonus_cometex3.png",
+  DATA_PREFIX "/images/comets/bonus_cometex3.png",
+  DATA_PREFIX "/images/comets/bonus_cometex2.png",
+  DATA_PREFIX "/images/comets/bonus_cometex2.png",
+  DATA_PREFIX "/images/comets/bonus_cometex1a.png",
+  DATA_PREFIX "/images/comets/bonus_cometex1a.png",
+  DATA_PREFIX "/images/comets/bonus_cometex1.png",
+  DATA_PREFIX "/images/comets/bonus_cometex1.png",
+  DATA_PREFIX "/images/status/nums.png",
+  DATA_PREFIX "/images/status/lednums.png",
+  DATA_PREFIX "/images/status/led_neg_sign.png",
+  DATA_PREFIX "/images/status/paused.png",
+  DATA_PREFIX "/images/status/demo.png",
+  DATA_PREFIX "/images/status/demo-small.png",
+  DATA_PREFIX "/images/status/keypad.png",
+  DATA_PREFIX "/images/status/keypad_no_neg.png",
+  DATA_PREFIX "/images/tux/console_led.png",
+  DATA_PREFIX "/images/tux/console_bash.png",
+  DATA_PREFIX "/images/tux/tux-console1.png",
+  DATA_PREFIX "/images/tux/tux-console2.png",
+  DATA_PREFIX "/images/tux/tux-console3.png",
+  DATA_PREFIX "/images/tux/tux-console4.png",
+  DATA_PREFIX "/images/tux/tux-relax1.png",
+  DATA_PREFIX "/images/tux/tux-relax2.png",
+  DATA_PREFIX "/images/tux/tux-egypt1.png",
+  DATA_PREFIX "/images/tux/tux-egypt2.png",
+  DATA_PREFIX "/images/tux/tux-egypt3.png",
+  DATA_PREFIX "/images/tux/tux-egypt4.png",
+  DATA_PREFIX "/images/tux/tux-drat.png",
+  DATA_PREFIX "/images/tux/tux-yipe.png",
+  DATA_PREFIX "/images/tux/tux-yay1.png",
+  DATA_PREFIX "/images/tux/tux-yay2.png",
+  DATA_PREFIX "/images/tux/tux-yes1.png",
+  DATA_PREFIX "/images/tux/tux-yes2.png",
+  DATA_PREFIX "/images/tux/tux-sit.png",
+  DATA_PREFIX "/images/tux/tux-fist1.png",
+  DATA_PREFIX "/images/tux/tux-fist2.png",
+  DATA_PREFIX "/images/penguins/flapdown.png",
+  DATA_PREFIX "/images/penguins/flapup.png",
+  DATA_PREFIX "/images/penguins/incoming.png",
+  DATA_PREFIX "/images/penguins/grumpy.png",
+  DATA_PREFIX "/images/penguins/worried.png",
+  DATA_PREFIX "/images/penguins/standing-up.png",
+  DATA_PREFIX "/images/penguins/sitting-down.png",
+  DATA_PREFIX "/images/penguins/walk-on1.png",
+  DATA_PREFIX "/images/penguins/walk-on2.png",
+  DATA_PREFIX "/images/penguins/walk-on3.png",
+  DATA_PREFIX "/images/penguins/walk-off1.png",
+  DATA_PREFIX "/images/penguins/walk-off2.png",
+  DATA_PREFIX "/images/penguins/walk-off3.png",
+  DATA_PREFIX "/images/igloos/melted3.png",
+  DATA_PREFIX "/images/igloos/melted2.png",
+  DATA_PREFIX "/images/igloos/melted1.png",
+  DATA_PREFIX "/images/igloos/half.png",
+  DATA_PREFIX "/images/igloos/intact.png",
+  DATA_PREFIX "/images/igloos/rebuilding1.png",
+  DATA_PREFIX "/images/igloos/rebuilding2.png",
+  DATA_PREFIX "/images/igloos/steam1.png",
+  DATA_PREFIX "/images/igloos/steam2.png",
+  DATA_PREFIX "/images/igloos/steam3.png",
+  DATA_PREFIX "/images/igloos/steam4.png",
+  DATA_PREFIX "/images/igloos/steam5.png",
+  DATA_PREFIX "/images/igloos/cloud.png",
+  DATA_PREFIX "/images/igloos/snow1.png",
+  DATA_PREFIX "/images/igloos/snow2.png",
+  DATA_PREFIX "/images/igloos/snow3.png",
+  DATA_PREFIX "/images/igloos/extra_life.png",
+  DATA_PREFIX "/images/status/wave.png",
+  DATA_PREFIX "/images/status/score.png",
+  DATA_PREFIX "/images/status/stop.png",
+  DATA_PREFIX "/images/status/numbers.png",
+  DATA_PREFIX "/images/status/gameover.png",
+  DATA_PREFIX "/images/status/gameover_won.png",
+  DATA_PREFIX "/images/factoroids/gbstars.png",
+  DATA_PREFIX "/images/factoroids/asteroid1.png",
+  DATA_PREFIX "/images/factoroids/asteroid2.png",
+  DATA_PREFIX "/images/factoroids/asteroid3.png",
+  DATA_PREFIX "/images/factoroids/ship01.png",
+  DATA_PREFIX "/images/factoroids/factoroids.png",
+  DATA_PREFIX "/images/factoroids/factors.png",
+  DATA_PREFIX "/images/factoroids/tux.png",
+  DATA_PREFIX "/images/factoroids/good.png"
+  };
+
+  /* Load images: */
+  for (i = 0; i < NUM_IMAGES; i++)
+  {
+    images[i] = IMG_Load(image_filenames[i]);
+
+    if (images[i] == NULL)
+    {
+      fprintf(stderr,
+              "\nError: I couldn't load a graphics file:\n"
+              "%s\n"
+              "The Simple DirectMedia error that occured was:\n"
+              "%s\n\n", image_filenames[i], SDL_GetError());
+      return 0;
+    }
+  }
+
+  glyph_offset = 0;
+
+#ifdef REPLACE_WAVESCORE  
+  /* Replace the "WAVE" and "SCORE" with translate-able versions */
+  TTF_Font *wavescore_font;
+  wavescore_font = LoadFont(DEFAULT_FONT_NAME, 28);
+  SDL_FreeSurface(images[IMG_WAVE]);
+  images[IMG_WAVE] = SimpleTextWithOffset(_("WAVE"), wavescore_font, &white, &glyph_offset);
+  SDL_FreeSurface(images[IMG_SCORE]);
+  images[IMG_SCORE] = SimpleTextWithOffset(_("SCORE"), wavescore_font, &white, &glyph_offset);
+  glyph_offset++;
+  TTF_CloseFont(wavescore_font);
+#endif
+
+  /* If we make it to here OK, return 1: */
+  return 1;
+}
+
+/* returns 1 if default font successfully loaded, 0 otherwise. */
+int load_default_font()
+{
+  default_font = LoadFont( DEFAULT_FONT_NAME,
+                           DEFAULT_MENU_FONT_SIZE);
+  help_font = LoadFont(    DEFAULT_FONT_NAME,
+                           DEFAULT_HELP_FONT_SIZE);
+
+  if (default_font && help_font)
+  {
+#ifdef TUXMATH_DEBUG
+    fprintf(stderr, "load_default_font(): %s loaded successfully\n\n",
+            DEFAULT_FONT_NAME);
+#endif
+    return 1;
+  }
+  else
+  {
+    fprintf(stderr, "LoadFont(): %s NOT loaded successfully. TTF: %s\n",
+            DEFAULT_FONT_NAME, TTF_GetError() );
+    return 0;
+  }
+}
+
+
+
+
+#ifndef NOSOUND
+int load_sound_data(void)
+{
+  int i = 0;
+
+  static char* sound_filenames[NUM_SOUNDS] = {
+  DATA_PREFIX "/sounds/harp.wav",
+  DATA_PREFIX "/sounds/pop.wav",
+  DATA_PREFIX "/sounds/tock.wav",
+  DATA_PREFIX "/sounds/laser.wav",
+  DATA_PREFIX "/sounds/buzz.wav",
+  DATA_PREFIX "/sounds/alarm.wav",
+  DATA_PREFIX "/sounds/shieldsdown.wav",
+  DATA_PREFIX "/sounds/explosion.wav",
+  DATA_PREFIX "/sounds/click.wav",
+  DATA_PREFIX "/sounds/sizzling.wav",
+  DATA_PREFIX "/sounds/towerclock.wav",
+  DATA_PREFIX "/sounds/cheer.wav"
+  };
+
+  static char* music_filenames[NUM_MUSICS] = {
+  DATA_PREFIX "/sounds/game.mod",
+  DATA_PREFIX "/sounds/game2.mod",
+  DATA_PREFIX "/sounds/game3.mod"
+  };
+
+  /* skip loading sound files if sound system not available: */
+  if (Opts_UsingSound())
+  {
+    for (i = 0; i < NUM_SOUNDS; i++)
+    {
+      sounds[i] = Mix_LoadWAV(sound_filenames[i]);
+
+      if (sounds[i] == NULL)
+      {
+        fprintf(stderr,
+                "\nError: I couldn't load a sound file:\n"
+                "%s\n"
+                "The Simple DirectMedia error that occured was:\n"
+                "%s\n\n", sound_filenames[i], SDL_GetError());
+        return 0;
+      }
+    }
+
+
+    for (i = 0; i < NUM_MUSICS; i++)
+    {
+      musics[i] = Mix_LoadMUS(music_filenames[i]);
+
+      if (musics[i] == NULL)
+      {
+        fprintf(stderr,
+                "\nError: I couldn't load a music file:\n"
+                "%s\n"
+                "The Simple DirectMedia error that occured was:\n"
+                "%s\n\n", music_filenames[i], SDL_GetError());
+        return 0;
+      }
+
+    }
+  }
+  return 1;
+}
+
+#endif /* NOSOUND */

Added: tuxmath/trunk/src/game.c
===================================================================
--- tuxmath/trunk/src/game.c	                        (rev 0)
+++ tuxmath/trunk/src/game.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,3469 @@
+/*
+  game.c
+
+  For TuxMath
+  The main game loop!
+
+  by Bill Kendrick
+  bill at newbreedsoftware.com
+  http://www.newbreedsoftware.com/
+
+
+  Part of "Tux4Kids" Project
+  http://www.tux4kids.org/
+
+  August 26, 2001 - February 18, 2004
+
+  Revised by David Bruce, Tim Holy and others
+  2005-2007
+*/
+
+/* put this first so we get <config.h> and <gettext.h> immediately: */
+#include "tuxmath.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "SDL.h"
+#ifndef NOSOUND
+#include "SDL_mixer.h"
+#endif
+#include "SDL_image.h"
+
+#include "game.h"
+#include "fileops.h"
+#include "setup.h"
+#include "loaders.h"
+#include "mathcards.h"
+#include "multiplayer.h"
+#include "titlescreen.h"
+#include "options.h"
+#include "SDL_extras.h"
+
+#define FPS 15                     /* 15 frames per second */
+#define MS_PER_FRAME (1000 / FPS)
+
+#define CITY_EXPL_START (3 * 5)  /* Must be mult. of 5 (number of expl frames) */
+#define ANIM_FRAME_START (4 * 2) /* Must be mult. of 2 (number of tux frames) */
+#define GAMEOVER_COUNTER_START 40
+#define LEVEL_START_WAIT_START 20
+#define LASER_START 5
+#define FLAPPING_START 12
+#define FLAPPING_INTERVAL 500
+#define STEAM_START 15
+#define IGLOO_SWITCH_START 8
+#define STANDING_COUNTER_START 8
+#define EVAPORATING_COUNTER_START 100
+
+#define PENGUIN_WALK_SPEED 3
+#define SNOWFLAKE_SPEED 6
+#define SNOWFLAKE_SEPARATION 3
+
+const int SND_IGLOO_SIZZLE = SND_SIZZLE;
+const int IMG_CITY_NONE = 0;
+
+typedef struct comet_type {
+  int alive;
+  int expl;
+  int city;
+  float x, y;
+  int answer;
+  int bonus;
+  int zapped;
+  MC_FlashCard flashcard;
+  Uint32 time_started;
+} comet_type;
+
+/* Local (to game.c) 'globals': */
+
+static int gameover_counter;
+static int game_status;
+static int SDL_quit_received;
+static int escape_received;
+static int paused;
+static int wave;
+static int score;
+static int pre_wave_score;
+static int prev_wave_comets;
+static int slowdown;
+static int num_attackers;
+static float speed;
+static int demo_countdown;
+static int tux_anim;
+static int tux_anim_frame;
+static int num_cities_alive;
+static int num_comets_alive;
+static int tux_img;
+static int old_tux_img;
+static int frame;
+static int neg_answer_picked;
+static int tux_pressing;
+static int doing_answer;
+static int level_start_wait;
+static int last_bkgd;
+static int igloo_vertical_offset;
+//static int extra_life_counter;
+static int bonus_comet_counter;
+static int extra_life_earned;
+static int key_pressed;
+
+/* Feedback-related variables */
+static int city_expl_height;
+static int comet_feedback_number;
+static float comet_feedback_height;
+static float danger_level;
+
+static int digits[MC_MAX_DIGITS];
+
+static comet_type* comets = NULL;
+static city_type* cities = NULL;
+static penguin_type* penguins = NULL;
+static steam_type* steam = NULL;
+
+static cloud_type cloud;
+static laser_type laser;
+static SDL_Surface* bkgd = NULL; //640x480 background (windowed)
+static SDL_Surface* scaled_bkgd = NULL; //native resolution (fullscreen)
+
+static SDL_Surface* current_bkgd()
+  { return screen->flags & SDL_FULLSCREEN ? scaled_bkgd : bkgd; }
+
+static game_message s1, s2, s3, s4, s5;
+static int start_message_chosen = 0;
+
+
+typedef struct {
+  int x_is_blinking;
+  int extra_life_is_blinking;
+  int laser_enabled;
+} help_controls_type;
+static help_controls_type help_controls;
+
+/* Local function prototypes: */
+static int  game_initialize(void);
+static void game_cleanup(void);
+static void game_handle_help(void);
+static void game_handle_user_events(void);
+static void game_handle_demo(void);
+static void game_handle_answer(void);
+static void game_countdown(void);
+static void game_handle_tux(void);
+static void game_handle_comets(void);
+static void game_handle_cities(void);
+static void game_handle_penguins(void);
+static void game_handle_steam(void);
+static void game_handle_extra_life(void);
+static void game_draw(void);
+static void game_draw_background(void);
+static void game_draw_comets(void);
+static void game_draw_cities(void);
+static void game_draw_misc(void);
+
+static int check_extra_life(void);
+static int check_exit_conditions(void);
+
+static void game_set_message(game_message *,const char *,int x, int y);
+static void game_clear_message(game_message*);
+static void game_clear_messages(void);
+static void game_write_message(const game_message* msg);
+static void game_write_messages(void);
+static void draw_led_console(void);
+static void draw_question_counter(void);
+static void draw_console_image(int i);
+
+static void reset_level(void);
+static int add_comet(void);
+static void add_score(int inc);
+static void reset_comets(void);
+
+static void game_mouse_event(SDL_Event event);
+static void game_key_event(SDLKey key);
+static void free_on_exit(void);
+
+static void help_add_comet(const char* formula_str, const char* ans_str);
+static int help_renderframe_exit(void);
+static void game_recalc_positions(void);
+
+void putpixel(SDL_Surface* surface, int x, int y, Uint32 pixel);
+
+#ifdef TUXMATH_DEBUG
+static void print_exit_conditions(void);
+static void print_status(void);
+#endif
+
+/* --- MAIN GAME FUNCTION!!! --- */
+
+
+int game(void)
+{
+  Uint32 last_time, now_time;
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "Entering game():\n");
+#endif
+
+  //see if the option matches the actual screen
+  if (Opts_GetGlobalOpt(FULLSCREEN) == !(screen->flags & SDL_FULLSCREEN) )
+    {
+    ;//SwitchScreenMode();
+    }
+
+
+   /* most code moved into smaller functions (game_*()): */
+  if (!game_initialize())
+  {
+    fprintf(stderr, "\ngame_initialize() failed!");
+    /* return 0 so we go back to Options screen - maybe */
+    /* player simply has all operations deselected */
+//    free_on_exit();
+    return 0;
+  }
+
+  if (Opts_HelpMode()) {
+    game_handle_help();
+    game_cleanup();
+    return GAME_OVER_OTHER;
+  }
+
+
+
+  /* --- MAIN GAME LOOP: --- */
+  do
+  {
+    /* reset or increment various things with each loop: */
+    frame++;
+    last_time = SDL_GetTicks();
+    old_tux_img = tux_img;
+    tux_pressing = 0;
+
+    if (laser.alive > 0)
+    {
+      laser.alive--;
+    }
+
+    /* Most code now in smaller functions: */
+    game_handle_user_events();
+    game_handle_demo();
+    game_handle_answer();
+    game_countdown();
+    game_handle_tux();
+    game_handle_comets();
+    game_handle_cities();
+    game_handle_penguins();
+    game_handle_steam();
+    game_handle_extra_life();
+    game_draw();
+    /* figure out if we should leave loop: */
+    game_status = check_exit_conditions();
+
+
+    /* If we're in "PAUSE" mode, pause! */
+    if (paused)
+    {
+      pause_game();
+      paused = 0;
+    }
+
+      /* Keep playing music: */
+
+#ifndef NOSOUND
+    if (Opts_UsingSound())
+    {
+      if (!Mix_PlayingMusic())
+      {
+            Mix_PlayMusic(musics[MUS_GAME + (rand() % 3)], 0);
+      }
+    }
+#endif
+
+    /* Pause (keep frame-rate event) */
+    now_time = SDL_GetTicks();
+    if (now_time < last_time + MS_PER_FRAME)
+    {
+      //Prevent any possibility of a time wrap-around
+      // (this is a very unlikely problem unless there is an SDL bug
+      //  or you leave tuxmath running for 49 days...)
+      now_time = (last_time+MS_PER_FRAME) - now_time;  // this holds the delay
+      if (now_time > MS_PER_FRAME)
+        now_time = MS_PER_FRAME;
+      SDL_Delay(now_time);
+    }
+  }
+  while(GAME_IN_PROGRESS == game_status);
+  /* END OF MAIN GAME LOOP! */
+
+#ifdef TUXMATH_DEBUG
+  print_exit_conditions();
+#endif
+
+  /* TODO: need better "victory" screen with animation, special music, etc., */
+  /* as well as options to review missed questions, play again using missed  */
+  /* questions as question list, etc.                                        */
+  switch (game_status)
+  {
+    SDL_Rect dest_message;
+    SDL_Rect dest_tux;
+    SDL_Event event;
+
+    case GAME_OVER_WON:
+    {
+      int looping = 1;
+      int tux_offset = 0;
+      int tux_step = -3;
+
+      /* set up victory message: */
+      dest_message.x = (screen->w - images[IMG_GAMEOVER_WON]->w) / 2;
+      dest_message.y = (screen->h - images[IMG_GAMEOVER_WON]->h) / 2;
+      dest_message.w = images[IMG_GAMEOVER_WON]->w;
+      dest_message.h = images[IMG_GAMEOVER_WON]->h;
+
+      do
+      {
+        frame++;
+        last_time = SDL_GetTicks();
+
+        while (SDL_PollEvent(&event) > 0)
+        {
+          if  (event.type == SDL_QUIT
+            || event.type == SDL_KEYDOWN
+            || event.type == SDL_MOUSEBUTTONDOWN)
+          {
+            looping = 0;
+          }
+        }
+
+        if (current_bkgd() )
+          SDL_BlitSurface(current_bkgd(), NULL, screen, NULL);
+
+
+
+        /* draw flashing victory message: */
+        if (((frame / 2) % 4))
+        {
+          SDL_BlitSurface(images[IMG_GAMEOVER_WON], NULL, screen, &dest_message);
+        }
+
+        /* draw dancing tux: */
+        draw_console_image(IMG_CONSOLE_BASH);
+        /* walk tux back and forth */
+        tux_offset += tux_step;
+        /* select tux_egypt images according to which way tux is headed: */
+        if (tux_step < 0)
+          tux_img = IMG_TUX_EGYPT1 + ((frame / 3) % 2);
+        else
+          tux_img = IMG_TUX_EGYPT3 + ((frame / 3) % 2);
+
+        /* turn around if we go far enough: */
+        if (tux_offset >= (screen->w)/2
+         || tux_offset <= -(screen->w)/2)
+        {
+          tux_step = -tux_step;
+        }
+
+        dest_tux.x = ((screen->w - images[tux_img]->w) / 2) + tux_offset;
+        dest_tux.y = (screen->h - images[tux_img]->h);
+        dest_tux.w = images[tux_img]->w;
+        dest_tux.h = images[tux_img]->h;
+
+        SDL_BlitSurface(images[tux_img], NULL, screen, &dest_tux);
+
+/*        draw_console_image(tux_img);*/
+
+        SDL_Flip(screen);
+
+        now_time = SDL_GetTicks();
+
+        if (now_time < last_time + MS_PER_FRAME)
+          SDL_Delay(last_time + MS_PER_FRAME - now_time);
+      }
+      while (looping);
+      break;
+    }
+
+    case GAME_OVER_ERROR:
+    {
+#ifdef TUXMATH_DEBUG
+      printf("\ngame() exiting with error");
+#endif
+    }
+    case GAME_OVER_LOST:
+    case GAME_OVER_OTHER:
+    {
+      int looping = 1;
+
+      /* set up GAMEOVER message: */
+      dest_message.x = (screen->w - images[IMG_GAMEOVER]->w) / 2;
+      dest_message.y = (screen->h - images[IMG_GAMEOVER]->h) / 2;
+      dest_message.w = images[IMG_GAMEOVER]->w;
+      dest_message.h = images[IMG_GAMEOVER]->h;
+
+      do
+      {
+        frame++;
+        last_time = SDL_GetTicks();
+
+        while (SDL_PollEvent(&event) > 0)
+        {
+          if  (event.type == SDL_QUIT
+            || event.type == SDL_KEYDOWN
+            || event.type == SDL_MOUSEBUTTONDOWN)
+          {
+            looping = 0;
+          }
+        }
+
+        SDL_BlitSurface(images[IMG_GAMEOVER], NULL, screen, &dest_message);
+        SDL_Flip(screen);
+
+        now_time = SDL_GetTicks();
+
+        if (now_time < last_time + MS_PER_FRAME)
+          SDL_Delay(last_time + MS_PER_FRAME - now_time);
+      }
+      while (looping);
+
+      break;
+    }
+
+    case GAME_OVER_ESCAPE:
+    {
+      break;
+    }
+
+    case GAME_OVER_WINDOW_CLOSE:
+    {
+      break;
+    }
+
+  }
+
+  game_cleanup();
+
+  /* Write post-game info to game summary file: */
+  if (Opts_SaveSummary())
+  {
+    write_postgame_summary();
+  }
+
+  /* Save score in case needed for high score table: */
+  Opts_SetLastScore(score);
+
+  /* Return the chosen command: */
+  if (GAME_OVER_WINDOW_CLOSE == game_status)
+  {
+    /* program exits: */
+    cleanup();
+    return 1;
+  }
+  else
+  {
+    /* return to title() screen: */
+    return game_status;
+  }
+}
+
+/* 
+Set one to four lines of text to display at the game's start. Eventually
+this should stylishly fade out over the first few moments of the game.
+*/
+void game_set_start_message(const char* m1, const char* m2, 
+                            const char* m3, const char* m4)
+{
+  game_set_message(&s1, m1, -1, RES_Y * 2 / 10);
+  game_set_message(&s2, m2, screen->w / 2 - 40, RES_Y * 3 / 10);
+  game_set_message(&s3, m3, screen->w / 2 - 40, RES_Y * 4 / 10);
+  game_set_message(&s4, m4, screen->w / 2 - 40, RES_Y * 5 / 10);
+  start_message_chosen = 1;
+}
+
+int game_initialize(void)
+{
+  int i,img;
+  
+  tmdprintf("Entering game_initialize()\n");
+
+  /* Clear window: */
+  SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));
+  SDL_Flip(screen);
+
+  game_status = GAME_IN_PROGRESS;
+  gameover_counter = -1;
+  SDL_quit_received = 0;
+  escape_received = 0;
+
+  /* Start MathCards backend: */
+  /* FIXME may need to move this into tuxmath.c to accomodate option */
+  /* to use MC_StartUsingWrongs() */
+  /* NOTE MC_StartGame() will return 0 if the list length is zero due */
+  /* (for example) to all math operations being deselected */
+  if (!MC_StartGame())
+  {
+    tmdprintf("\nMC_StartGame() failed!");
+    fprintf(stderr, "\nMC_StartGame() failed!");
+    return 0;
+  }
+
+  /* Allocate memory */
+  comets = NULL;  // set in case allocation fails partway through
+  cities = NULL;
+  penguins = NULL;
+  steam = NULL;
+  comets = (comet_type *) malloc(MAX_MAX_COMETS * sizeof(comet_type));
+  if (comets == NULL) {
+    printf("Allocation of comets failed");
+    return 0;
+  }
+  else {
+    for (i = 0; i < MAX_MAX_COMETS; ++i)
+      {
+      comets[i].flashcard = MC_AllocateFlashcard();
+      if (!MC_FlashCardGood(&comets[i].flashcard) ) 
+        {
+        //something's wrong
+        printf("Allocation of flashcard %d failed\n", i);
+        for (; i >= 0; --i) //free anything we've already gotten
+          MC_FreeFlashcard(&comets[i].flashcard);
+        return 0;
+        }
+      }
+  }
+  
+  cities = (city_type *) malloc(NUM_CITIES * sizeof(city_type));
+  if (cities == NULL) {
+    printf("Allocation of cities failed");
+    return 0;
+  }
+  penguins = (penguin_type *) malloc(NUM_CITIES * sizeof(penguin_type));
+  if (penguins == NULL) {
+    printf("Allocation of penguins failed");
+    return 0;
+  }
+  steam = (steam_type *) malloc(NUM_CITIES * sizeof(steam_type));
+  if (steam == NULL) {
+    printf("Allocation of steam failed");
+    return 0;
+  }
+
+  
+  /* Write pre-game info to game summary file: */
+  if (Opts_SaveSummary())
+  {
+    write_pregame_summary();
+  }
+
+  /* Prepare to start the game: */
+  city_expl_height = screen->h - images[IMG_CITY_BLUE]->h;
+
+  /* Initialize feedback parameters */
+  comet_feedback_number = 0;
+  comet_feedback_height = 0;
+  danger_level = Opts_DangerLevel();
+
+  wave = 1;
+  num_attackers = 2;
+  prev_wave_comets = Opts_StartingComets();
+  speed = Opts_Speed();
+  slowdown = 0;
+  score = 0;
+  demo_countdown = 2000;
+  level_start_wait = LEVEL_START_WAIT_START;
+  neg_answer_picked = 0;
+
+  /* (Create and position cities) */
+
+  if (Opts_GetGlobalOpt(USE_IGLOOS))
+    img = IMG_IGLOO_INTACT;
+  else
+    img = IMG_CITY_BLUE;
+  for (i = 0; i < NUM_CITIES; i++)
+  {
+    cities[i].hits_left = 2;
+    cities[i].status = CITY_PRESENT;
+    cities[i].counter = 0;
+    cities[i].threatened = 0;
+    cities[i].layer = 0;
+
+    /* Left vs. Right - makes room for Tux and the console */
+    if (i < NUM_CITIES / 2)
+    {
+      cities[i].x = (((screen->w / (NUM_CITIES + 1)) * i) +
+                     ((images[img] -> w) / 2));
+    }
+    else
+    {
+      cities[i].x = (screen->w -
+                 ((((screen->w / (NUM_CITIES + 1)) *
+                    (i - (NUM_CITIES / 2)) +
+                   ((images[img] -> w) / 2)))));
+    }
+  }
+
+  num_cities_alive = NUM_CITIES;
+  num_comets_alive = 0;
+
+  igloo_vertical_offset = images[IMG_CITY_BLUE]->h - images[IMG_IGLOO_INTACT]->h;
+
+  /* Create and position the penguins and steam */
+  for (i = 0; i < NUM_CITIES; i++) {
+    penguins[i].status = PENGUIN_HAPPY;
+    penguins[i].counter = 0;
+    penguins[i].x = cities[i].x;
+    penguins[i].layer = 0;
+    steam[i].status = STEAM_OFF;
+    steam[i].layer = 0;
+    steam[i].counter = 0;
+  }
+
+  if (Opts_BonusCometInterval()) {
+    bonus_comet_counter = Opts_BonusCometInterval() + 1;
+    tmdprintf("\nInitializing with bonus_comet_counter = %d\n",bonus_comet_counter);
+  }
+  extra_life_earned = 0;
+  cloud.status = EXTRA_LIFE_OFF;
+
+  /* (Clear laser) */
+  laser.alive = 0;
+
+  /* Reset remaining stuff: */
+
+  bkgd = scaled_bkgd = NULL;
+  last_bkgd = -1;
+  reset_level();
+  reset_comets();
+
+  frame = 0;
+  paused = 0;
+  doing_answer = 0;
+  tux_pressing = 0;
+  tux_img = IMG_TUX_RELAX1;
+  tux_anim = -1;
+  tux_anim_frame = 0;
+
+  // Initialize the messages
+  game_clear_message(&s5);
+  if (!start_message_chosen)
+  {
+    game_clear_message(&s1);
+    game_clear_message(&s2);
+    game_clear_message(&s3);
+    game_clear_message(&s4);
+  }
+
+  help_controls.x_is_blinking = 0;
+  help_controls.extra_life_is_blinking = 0;
+  help_controls.laser_enabled = 1;
+
+  return 1;
+}
+
+
+void game_cleanup(void)
+{
+  /* Free background: */
+  if (bkgd != NULL)
+  {
+    SDL_FreeSurface(bkgd);
+    bkgd = NULL;
+  }
+  if (scaled_bkgd != NULL)
+  {
+    SDL_FreeSurface(scaled_bkgd);
+    scaled_bkgd = NULL;
+  }
+  
+  /* clear start message */
+  start_message_chosen = 0;
+  
+  /* Free dynamically-allocated items */
+  free_on_exit();
+
+  /* Stop music: */
+#ifndef NOSOUND
+  if (Opts_UsingSound())
+  {
+    if (Mix_PlayingMusic())
+    {
+      Mix_HaltMusic();
+    }
+  }
+#endif
+
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "Leaving game():\n");
+#endif
+
+}
+
+void game_handle_help(void)
+{
+  const int left_edge = 140;
+  int frame_start;
+  int quit_help = 0;
+
+  help_controls.laser_enabled = 0;
+  help_controls.x_is_blinking = 0;
+  help_controls.extra_life_is_blinking = 0;
+
+  // Here are some things that have to happen before we can safely
+  // draw the screen
+  tux_img = IMG_TUX_CONSOLE1;
+  old_tux_img = tux_img;
+  tux_pressing = 0;
+  frame = 0;
+
+  // Write the introductory text
+  game_set_message(&s1,_("Welcome to TuxMath!"),-1,50);
+
+#ifndef NOSOUND
+  if (Opts_UsingSound())
+  {
+    if (!Mix_PlayingMusic())
+    {
+      Mix_PlayMusic(musics[MUS_GAME], 0);
+    }
+  }
+#endif
+
+  // Wait 2 seconds while rendering frames
+  while (frame < 2*FPS && !(quit_help = help_renderframe_exit()));
+  if (quit_help)
+    return;
+
+  game_set_message(&s2,_("Your mission is to save your"), left_edge, 100);
+  game_set_message(&s3,_("penguins' igloos from the"), left_edge, 135);
+  game_set_message(&s4,_("falling comets."), left_edge, 170);
+
+  frame_start = frame;
+  while (frame-frame_start < 5*FPS && !(quit_help = help_renderframe_exit()));  // wait 5 more secs
+  if (quit_help)
+    return;
+
+  // Bring in a comet
+  speed = 2;
+  help_add_comet("2 + 1 = ?", "3");
+  help_controls.laser_enabled = 1;
+  level_start_wait = 0;
+
+  frame_start = frame;
+  while (comets[0].alive && frame-frame_start < 100 && !(quit_help = help_renderframe_exit())); // advance comet
+  if (quit_help)
+    return;
+
+  if (comets[0].alive == 1) {
+    game_set_message(&s1,_("Stop a comet by typing"),left_edge,100);
+    game_set_message(&s2,_("the answer to the math problem"),left_edge,135);
+    game_set_message(&s3,_("and hitting 'space' or 'enter'."),left_edge,170);
+    game_set_message(&s4,_("Try it now!"),left_edge,225);
+
+    speed = 0;
+    while (comets[0].alive && !(quit_help = help_renderframe_exit()));
+    if (quit_help)
+      return;
+  }
+
+  game_set_message(&s1,_("Good shot!"),left_edge,100);
+  game_clear_message(&s2);
+  game_clear_message(&s3);
+  game_clear_message(&s4);
+
+  help_controls.laser_enabled = 0;
+  frame_start = frame;
+  while (frame-frame_start < 3*FPS && !(quit_help = help_renderframe_exit()));  // wait 3 secs
+
+  speed = 2;
+  game_set_message(&s1,_("If an igloo gets hit by a comet,"),left_edge,100);
+  game_set_message(&s2,_("it melts. But don't worry: the"),left_edge,135);
+  game_set_message(&s3,_("penguin is OK!"),left_edge,170);
+  game_set_message(&s4,_("Just watch what happens:"),left_edge,225);
+  game_set_message(&s5,_("(Press a key to start)"),left_edge,260);
+
+  key_pressed = 0;
+  while (!key_pressed && !(quit_help = help_renderframe_exit()));
+  if (quit_help)
+    return;
+  game_clear_message(&s5);
+
+  help_add_comet("3 * 3 = ?", "9");
+  comets[0].y = 2*(screen->h)/3;   // start it low down
+  while (!(comets[0].expl) && !(quit_help = help_renderframe_exit()));  // wait 3 secs
+  if (quit_help)
+    return;
+  game_set_message(&s4,_("Notice the answer"),left_edge,comets[0].y-100);
+  help_renderframe_exit();
+  SDL_Delay(4000);
+  game_clear_message(&s4);
+
+  frame_start = frame;
+  while (frame-frame_start < 5*FPS && !(quit_help = help_renderframe_exit()));  // wait 5 secs
+  if (quit_help)
+    return;
+
+  game_set_message(&s1,_("If it gets hit again, the"),left_edge,100);
+  game_set_message(&s2,_("penguin leaves."),left_edge,135);
+  game_set_message(&s3,_("(Press a key when ready)"),left_edge,200);
+
+  key_pressed = 0;
+  while (!key_pressed && !(quit_help = help_renderframe_exit()));
+  if (quit_help)
+    return;
+  game_clear_message(&s3);
+
+  help_add_comet("56 / 8 = ?", "7");
+  comets[0].y = 2*(screen->h)/3;   // start it low down
+  while (comets[0].alive && !(quit_help = help_renderframe_exit()));
+  if (quit_help)
+    return;
+  frame_start = frame;
+  while ((frame-frame_start < 3*FPS) && !(quit_help = help_renderframe_exit()));
+  if (quit_help)
+    return;
+
+  help_controls.laser_enabled = 1;
+  game_set_message(&s1,_("You can fix the igloos"),left_edge,100);
+  game_set_message(&s2,_("by stopping bonus comets."),left_edge,135);
+  help_add_comet("2 + 2 = ?", "4");
+  comets[0].bonus = 1;
+  frame_start = frame;
+  while (comets[0].alive && (frame-frame_start < 50) && !(quit_help = help_renderframe_exit()));
+  if (quit_help)
+    return;
+  if (comets[0].alive)
+    speed = 0;
+  game_set_message(&s3,_("Zap it now!"),left_edge,225);
+  while (comets[0].alive && !(quit_help = help_renderframe_exit()));
+  if (quit_help)
+    return;
+  game_set_message(&s1,_("Great job!"),left_edge,100);
+  game_clear_message(&s2);
+  game_clear_message(&s3);
+  frame_start = frame;
+  while ((frame-frame_start < 2*FPS) && !(quit_help = help_renderframe_exit()));
+  if (quit_help)
+    return;
+  check_extra_life();
+  frame_start = frame;
+  while ((frame-frame_start < 10*FPS) && !(quit_help = help_renderframe_exit()));
+  if (quit_help)
+    return;
+
+
+  game_set_message(&s1,_("Quit at any time by pressing"),left_edge,100);
+  game_set_message(&s2,_("'Esc' or clicking the 'X'"),left_edge,135);
+  game_set_message(&s3,_("in the upper right corner."),left_edge,170);
+  game_set_message(&s4,_("Do it now, and then play!"),left_edge,225);
+
+  help_controls.x_is_blinking = 1;
+  while (!help_renderframe_exit());
+}
+
+// This function handles all the interactions expected during help
+// screens and renders a single frame. This function normally returns
+// 0, but returns 1 if the user chooses to exit help.
+int help_renderframe_exit(void)
+{
+  static Uint32 last_time = 0;
+  static Uint32 now_time;
+
+  if (last_time == 0)
+    last_time = SDL_GetTicks(); // Initialize...
+
+  tux_pressing = 0;
+  if (laser.alive > 0)
+      laser.alive--;
+  game_handle_user_events();
+  game_handle_answer();
+  game_handle_tux();
+  game_handle_comets();
+  game_handle_cities();
+  game_handle_penguins();
+  game_handle_steam();
+  game_handle_extra_life();
+  game_draw();
+  game_status = check_exit_conditions();
+
+  // Delay to keep frame rate constant. Do this in a way
+  // that won't cause a freeze if the timer wraps around.
+  now_time = SDL_GetTicks();
+  if (now_time >= last_time && now_time < last_time + MS_PER_FRAME)
+    SDL_Delay((last_time+MS_PER_FRAME) - now_time);
+  last_time = now_time;
+
+  frame++;
+
+  return (game_status != GAME_IN_PROGRESS);
+}
+
+/* explicitly create a comet with a hardcoded problem */
+void help_add_comet(const char* formula_str, const char* ans_str)
+{
+//  char probstr[MC_FORMULA_LEN];
+//  char ansstr[MC_ANSWER_LEN];
+
+  comets[0].alive = 1;
+  comets[0].expl = 0;
+  comets[0].answer = atoi(ans_str);
+  num_comets_alive = 1;
+  comets[0].city = 0;
+  comets[0].x = cities[0].x;
+  comets[0].y = 0;
+  comets[0].zapped = 0;
+  comets[0].bonus = 0;
+
+  strncpy(comets[0].flashcard.formula_string,formula_str,MC_MaxFormulaSize() );
+  strncpy(comets[0].flashcard.answer_string,ans_str,MC_MaxAnswerSize() );
+}
+
+void game_set_message(game_message *msg,const char *txt,int x,int y)
+{
+  if (msg && txt)
+  {
+    msg->x = x;
+    msg->y = y;
+    msg->alpha = SDL_ALPHA_OPAQUE;
+    strncpy(msg->message,txt,GAME_MESSAGE_LENGTH);
+  }
+}
+
+void game_clear_message(game_message *msg)
+{
+  game_set_message(msg,"",0,0);
+}
+
+void game_clear_messages()
+{
+  game_clear_message(&s1);
+  game_clear_message(&s2);
+  game_clear_message(&s3);
+  game_clear_message(&s4);
+  game_clear_message(&s5);
+}
+
+void game_write_message(const game_message *msg)
+{
+  SDL_Surface *surf;
+  SDL_Rect rect;
+
+  if (strlen(msg->message) > 0) {
+    surf = BlackOutline( _(msg->message), help_font, &white);
+    rect.w = surf->w;
+    rect.h = surf->h;
+    if (msg->x < 0)
+      rect.x = (screen->w/2) - (rect.w/2);   // centered
+    else
+      rect.x = msg->x;              // left justified
+    rect.y = msg->y;
+    //FIXME alpha blending doesn't seem to work properly
+    SDL_SetAlpha(surf, SDL_SRCALPHA, msg->alpha);
+    SDL_BlitSurface(surf, NULL, screen, &rect);
+    SDL_FreeSurface(surf);
+    //SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
+  }
+}
+
+void game_write_messages(void)
+{
+  game_write_message(&s1);
+  game_write_message(&s2);
+  game_write_message(&s3);
+  game_write_message(&s4);
+  game_write_message(&s5);
+}
+
+void game_handle_user_events(void)
+{
+  SDL_Event event;
+  SDLKey key;
+
+  while (SDL_PollEvent(&event) > 0)
+  {
+    if (event.type == SDL_QUIT)
+    {
+      SDL_quit_received = 1;
+    }
+    else if (event.type == SDL_KEYDOWN)
+    {
+      key = event.key.keysym.sym;
+      game_key_event(key);
+    }
+    else if (event.type == SDL_MOUSEBUTTONDOWN)
+    {
+      game_mouse_event(event);
+    }
+  }
+}
+
+void game_handle_demo(void)
+{
+  /* If not in demo mode get out: */
+  if (!Opts_DemoMode())
+  {
+    return;
+  }
+
+  /* Demo mode! */
+  {
+  static int demo_answer = 0;
+  static int answer_digit = 0;
+  static int picked_comet=-1;
+
+  if (picked_comet == -1 && (rand() % 10) < 3)
+  {
+    /* Demo mode!  Randomly pick a comet to destroy: */
+    picked_comet = (rand() % MAX_COMETS);
+
+    if (!(comets[picked_comet].alive &&
+          comets[picked_comet].expl < COMET_EXPL_END)
+        || comets[picked_comet].y < 80)
+    {
+      picked_comet = -1;
+    }
+    else
+    {
+      /* found a comet to blow up! */
+      demo_answer = comets[picked_comet].answer;
+      if ((rand() % 3) < 1)
+        demo_answer--;  // sometimes get it wrong on purpose
+
+      #ifdef TUXMATH_DEBUG
+      printf("Demo mode, comet %d attacked with answer %d\n",picked_comet,demo_answer);
+      #endif
+      /* handle negative answer: */
+      if (demo_answer < 0)
+      {
+        demo_answer = -demo_answer;
+        neg_answer_picked = 1;
+      }
+      if (demo_answer >= 100)
+        answer_digit = 0;
+      else if (demo_answer >= 10)
+        answer_digit = 1;
+      else
+        answer_digit = 2;
+    }
+  }
+
+  /* Add a digit: */
+  if (picked_comet != -1 && (frame % 5) == 0 && (rand() % 10) < 8)
+  {
+    tux_pressing = 1;
+
+    if (answer_digit < 3)
+    {
+      digits[0] = digits[1];
+      digits[1] = digits[2];
+
+      if (answer_digit == 0)
+      {
+        digits[2] = demo_answer / 100;
+      }
+      else if (answer_digit == 1)
+      {
+        digits[2] = (demo_answer % 100) / 10;
+      }
+      else if (answer_digit == 2)
+      {
+        digits[2] = (demo_answer % 10);
+      }
+
+      answer_digit++;
+    }
+    else
+    {
+      /* "Press Return" */
+      #ifdef TUXMATH_DEBUG
+      printf("Demo mode firing with these digits: %d%d%d\n",digits[0],digits[1],digits[2]);
+      #endif
+      doing_answer = 1;
+      picked_comet = -1;
+    }
+  }
+
+  /* Count down counter: */
+  demo_countdown--;
+}
+}
+
+void game_handle_answer(void)
+{
+  int i, j, lowest, lowest_y;
+  char ans[MC_MAX_DIGITS+2]; //extra space for negative, and for final '\0'
+  Uint32 ctime;
+
+  if (!doing_answer)
+  {
+    return;
+  }
+
+  doing_answer = 0;
+/*
+  num = (digits[0] * 100 +
+         digits[1] * 10 +
+         digits[2]);
+*/
+  /* negative answer support DSB */
+  
+  ans[0] = '-'; //for math questions only, this is just replaced.
+  for (i = 0; i < MC_MAX_DIGITS - 1 && !digits[i]; ++i); //skip leading 0s
+  for (j = neg_answer_picked ? 1 : 0; i < MC_MAX_DIGITS; ++i, ++j)
+    ans[j] = digits[i] + '0';
+  ans[j] = '\0';
+  
+/*
+  if (neg_answer_picked)
+  {
+    ans[0] = '-';
+    for (i = j = 0; i < MC_MAX_DIGITS; ++i)
+    {
+      if (digits[i] == 0)
+        continue;
+      ans[++j] = digits[i] + '0';
+    }
+  }
+  else
+  {
+    for (i = j = 0; i < MC_MAX_DIGITS; ++i)
+    {
+      if (digits[i] == 0)
+        continue;
+      ans[j++] = digits[i] + '0';
+    }
+  } 
+*/
+
+  /*  Pick the lowest comet which has the right answer: */
+  /*  FIXME: do we want it to prefer bonus comets to regular comets? */
+  lowest_y = 0;
+  lowest = -1;
+
+  for (i = 0; i < MAX_COMETS; i++)
+  {
+    mcdprintf("Comparing '%s' with '%s'\n", comets[i].flashcard.answer_string, ans);
+    if (comets[i].alive &&
+        comets[i].expl < COMET_EXPL_END &&
+        //comets[i].answer == num &&
+        0 == strncmp(comets[i].flashcard.answer_string, ans, MC_MAX_DIGITS+1) &&
+        comets[i].y > lowest_y)
+    {
+      lowest = i;
+      lowest_y = comets[i].y;
+    }
+  }
+
+  /* If there was an comet with this answer, destroy it! */
+  if (lowest != -1)  /* -1 means no comet had this answer */
+  {
+    MC_AnsweredCorrectly(&(comets[lowest].flashcard));
+
+    /* Store the time the question was present on screen (do this */
+    /* in a way that avoids storing it if the time wrapped around */
+    ctime = SDL_GetTicks();
+    if (ctime > comets[lowest].time_started) {
+      MC_AddTimeToList((float)(ctime - comets[lowest].time_started)/1000);
+    }
+
+
+    /* Destroy comet: */
+    comets[lowest].expl = COMET_EXPL_START;
+    comets[lowest].zapped = 1;
+    /* Fire laser: */
+    laser.alive = LASER_START;
+    laser.x1 = screen->w / 2;
+    laser.y1 = screen->h;
+    laser.x2 = comets[lowest].x;
+    laser.y2 = comets[lowest].y;
+    playsound(SND_LASER);
+    playsound(SND_SIZZLE);
+
+    /* Record data for feedback */
+    if (Opts_UseFeedback())
+    {
+      comet_feedback_number++;
+      comet_feedback_height += comets[lowest].y/city_expl_height;
+
+#ifdef FEEDBACK_DEBUG
+      printf("Added comet feedback with height %g\n",comets[lowest].y/city_expl_height);
+#endif
+    }
+
+
+    /* FIXME maybe should move this into game_handle_tux() */
+    /* 50% of the time.. */
+    if ((rand() % 10) < 5)
+    {
+      /* ... pick an animation to play: */
+      if ((rand() % 10) < 5)
+        tux_anim = IMG_TUX_YES1;
+      else
+        tux_anim = IMG_TUX_YAY1;
+      tux_anim_frame = ANIM_FRAME_START;
+    }
+
+    /* Increment score: */
+
+    /* [ add = 25, sub = 50, mul = 75, div = 100 ] */
+    /* [ the higher the better ] */
+    /* FIXME looks like it might score a bit differently based on screen mode? */
+    add_score(25 * comets[lowest].flashcard.difficulty *
+              (screen->h - comets[lowest].y + 1) /
+               screen->h);
+  }
+  else
+  {
+    /* Didn't hit anything! */
+    laser.alive = LASER_START;
+    laser.x1 = screen->w / 2;
+    laser.y1 = screen->h;
+    laser.x2 = laser.x1;
+    laser.y2 = 0;
+    playsound(SND_LASER);
+    playsound(SND_BUZZ);
+
+    if ((rand() % 10) < 5)
+      tux_img = IMG_TUX_DRAT;
+    else
+      tux_img = IMG_TUX_YIPE;
+  }
+
+  /* Clear digits: */
+  for (i = 0; i < MC_MAX_DIGITS; ++i)
+    digits[i] = 0;
+  neg_answer_picked = 0;
+}
+
+void game_countdown(void)
+{
+  if (level_start_wait <= 0)
+  {
+    game_clear_messages();
+    return;
+  }
+
+  //dim start messages
+  s1.alpha -= SDL_ALPHA_OPAQUE / LEVEL_START_WAIT_START;
+  s2.alpha -= SDL_ALPHA_OPAQUE / LEVEL_START_WAIT_START;
+  s3.alpha -= SDL_ALPHA_OPAQUE / LEVEL_START_WAIT_START;
+  s4.alpha -= SDL_ALPHA_OPAQUE / LEVEL_START_WAIT_START;
+  tmdprintf("alpha = %d\n", s1.alpha);
+
+  level_start_wait--;
+  if (level_start_wait > LEVEL_START_WAIT_START / 4)
+    tux_img = IMG_TUX_RELAX1;
+  else if (level_start_wait > 0)
+    tux_img = IMG_TUX_RELAX2;
+  else
+    tux_img = IMG_TUX_SIT;
+
+  if (level_start_wait == LEVEL_START_WAIT_START / 4)
+  {
+    playsound(SND_ALARM);
+  }
+}
+
+void game_handle_tux(void)
+{
+  static int tux_same_counter;
+  /* If Tux pressed a button, pick a new (different!) stance: */
+  if (tux_pressing)
+  {
+
+    do
+    {
+      tux_img = IMG_TUX_CONSOLE1 + (rand() % 4);
+    }
+    while (tux_img == old_tux_img);
+
+    playsound(SND_CLICK);
+  }
+
+  /* If Tux is being animated, show the animation: */
+  if (tux_anim != -1)
+  {
+    tux_anim_frame--;
+    if (tux_anim_frame < 0)
+      tux_anim = -1;
+    else
+      tux_img = tux_anim + 1 - (tux_anim_frame / (ANIM_FRAME_START / 2));
+  }
+
+  /* Reset Tux to sitting if he's been doing nothing for a while: */
+  if (old_tux_img == tux_img)
+  {
+    tux_same_counter++;
+    if (tux_same_counter >= 20)
+    {
+      tux_img = IMG_TUX_SIT;
+    }
+  }
+  else
+    tux_same_counter = 0;
+}
+
+//FIXME might be simpler to store vertical position (and speed) in terms of time
+//rather than absolute position, and determine the latter in game_draw_comets()
+void game_handle_comets(void)
+{
+  /* Handle comets. Since the comets also are the things that trigger
+     changes in the cities, we set some flags in them, too. */
+  int i, this_city;
+  Uint32 ctime;
+
+  num_comets_alive = 0;
+
+  /* Clear the threatened flag on each city */
+  for (i = 0; i < NUM_CITIES; i++)
+    cities[i].threatened = 0;
+
+  for (i = 0; i < MAX_COMETS; i++)
+  {
+    if (comets[i].alive)
+    {
+      num_comets_alive++;
+      this_city = comets[i].city;
+
+      /* Update comet position */
+      comets[i].x = comets[i].x + 0; /* no lateral motion for now! */
+      /* Make bonus comet move faster at chosen ratio: */
+      if (comets[i].bonus)
+      {
+        comets[i].y += speed * Opts_BonusSpeedRatio() *
+                       city_expl_height / (RES_Y - images[IMG_CITY_BLUE]->h);
+      }
+      else /* Regular comet: */
+      {
+        comets[i].y += speed *
+                       city_expl_height / (RES_Y - images[IMG_CITY_BLUE]->h);
+      }
+
+      /* Does it threaten a city? */
+      if (comets[i].y > 3 * screen->h / 4)
+        cities[this_city].threatened = 1;
+
+      /* Did it hit a city? */
+      if (comets[i].y >= city_expl_height &&
+          comets[i].expl < COMET_EXPL_END)
+      {
+        /* Tell MathCards about it - question not answered correctly: */
+        MC_NotAnsweredCorrectly(&(comets[i].flashcard));
+
+        /* Store the time the question was present on screen (do this */
+        /* in a way that avoids storing it if the time wrapped around */
+        ctime = SDL_GetTicks();
+        if (ctime > comets[i].time_started) {
+          MC_AddTimeToList((float)(ctime - comets[i].time_started)/1000);
+        }
+
+        /* Record data for speed feedback */
+        /* Do this only for cities that are alive; dead cities */
+        /* might not get much protection from the player */
+        if (Opts_UseFeedback() && cities[this_city].hits_left) {
+          comet_feedback_number++;
+          comet_feedback_height += 1.0 + Opts_CityExplHandicap();
+
+#ifdef FEEDBACK_DEBUG
+           printf("Added comet feedback with height %g\n",
+                  1.0 + Opts_CityExplHandicap());
+#endif
+         }
+
+        /* Disable shields/destroy city/create steam cloud: */
+        if (cities[this_city].hits_left)
+        {
+          cities[this_city].status = CITY_EXPLODING;
+          if (Opts_GetGlobalOpt(USE_IGLOOS)) {
+            playsound(SND_IGLOO_SIZZLE);
+            cities[this_city].counter = IGLOO_SWITCH_START;
+            steam[this_city].status = STEAM_ON;
+            steam[this_city].counter = STEAM_START;
+          }
+          else {
+            if (cities[comets[i].city].hits_left == 2) {
+              playsound(SND_SHIELDSDOWN);
+              cities[this_city].counter = 1;  /* Will act immediately */
+            }
+            else {
+              playsound(SND_EXPLOSION);
+              cities[this_city].counter = CITY_EXPL_START;
+            }
+          }
+          cities[this_city].hits_left--;
+        }
+
+        /* If this was a bonus comet, restart the counter */
+        if (comets[i].bonus)
+          bonus_comet_counter = Opts_BonusCometInterval()+1;
+
+       /* If slow_after_wrong selected, set flag to go back to starting speed and */
+        /* number of attacking comets: */
+        if (Opts_SlowAfterWrong())
+        {
+          speed = Opts_Speed();
+          slowdown = 1;
+        }
+
+        tux_anim = IMG_TUX_FIST1;
+        tux_anim_frame = ANIM_FRAME_START;
+
+        /* Destroy comet: */
+        comets[i].expl = COMET_EXPL_START;
+      }
+
+      /* Handle comet explosion animation: */
+      if (comets[i].expl >= COMET_EXPL_END)
+      {
+        comets[i].expl--;
+        if (comets[i].expl < COMET_EXPL_END) {
+          comets[i].alive = 0;
+          if (bonus_comet_counter > 1 && comets[i].zapped) {
+            bonus_comet_counter--;
+#ifdef TUXMATH_DEBUG
+            printf("\nbonus_comet_counter is now %d\n",bonus_comet_counter);
+#endif
+          }
+          if (comets[i].bonus && comets[i].zapped) {
+            playsound(SND_EXTRA_LIFE);
+            extra_life_earned = 1;
+#ifdef TUXMATH_DEBUG
+            printf("\nExtra life earned!");
+#endif
+          }
+        }
+      }
+    }
+  }
+
+  /* add more comets if needed: */
+  if (!Opts_HelpMode() && level_start_wait == 0 &&
+      (frame % 20) == 0)   /* FIXME:do we want this to vary with comet speed?*/
+  {
+    /* num_attackers is how many comets are left in wave */
+    if (num_attackers > 0)
+    {
+      if ((rand() % 2) == 0 || num_comets_alive == 0)
+      {
+        if (add_comet())
+        {
+          num_attackers--;
+        }
+      }
+    }
+    else
+    {
+      if (num_comets_alive == 0)
+      {
+        if (!check_extra_life()) {
+          /* Time for the next wave! */
+          wave++;
+          reset_level();
+        }
+      }
+    }
+  }
+}
+
+
+
+void game_handle_cities(void)
+{
+  /* Update the status of the cities. These also determine the changes
+     in the penguins. */
+  int i;
+  num_cities_alive = 0;
+
+  for (i = 0; i < NUM_CITIES; i++)
+  {
+    /* Note: when status is CITY_REBUILDING, status and image
+       selection is handled by the extra_life code */
+    if (cities[i].status == CITY_REBUILDING)
+      continue;
+    if (cities[i].hits_left)
+      num_cities_alive++;
+    /* Handle counter for animated explosion: */
+    if (cities[i].status == CITY_EXPLODING)
+    {
+      cities[i].counter--;
+      if (cities[i].counter == 0) {
+        if (cities[i].hits_left)
+          cities[i].status = CITY_PRESENT;
+        else {
+          if (Opts_GetGlobalOpt(USE_IGLOOS)) {
+            cities[i].status = CITY_EVAPORATING;
+            cities[i].counter = EVAPORATING_COUNTER_START;
+            cities[i].img = IMG_IGLOO_MELTED1;
+          } else {
+            cities[i].status = CITY_GONE;
+            cities[i].img = IMG_CITY_NONE;
+          }
+        }
+      }
+    }
+    /* Choose the correct city/igloo image */
+    if (Opts_GetGlobalOpt(USE_IGLOOS)) {
+      if (cities[i].status == CITY_EVAPORATING) {
+        /* Handle the evaporation animation */
+        cities[i].layer = 0;  /* these have to be drawn below the penguin */
+        cities[i].counter--;
+        if (cities[i].counter == 0) {
+          cities[i].img--;
+          if (cities[i].img < IMG_IGLOO_MELTED3) {
+            cities[i].img = IMG_CITY_NONE;
+            cities[i].status = CITY_GONE;
+          }
+          else
+            cities[i].counter = EVAPORATING_COUNTER_START;
+        }
+      }        else {
+        if (cities[i].status != CITY_GONE) {
+          cities[i].layer = 1;  /* these have to be drawn above the penguin */
+          cities[i].img = IMG_IGLOO_MELTED1 + cities[i].hits_left;
+          /* If we're in the middle of an "explosion," don't switch to the
+             new igloo. Note the steam may have a different counter than
+             the igloo on this matter; the switch is designed to occur
+             halfway through the steam cloud. */
+          if (cities[i].status == CITY_EXPLODING)
+            cities[i].img++;
+        }
+      }
+    }
+    else {
+      /* We're using the original "city" graphics */
+      cities[i].layer = 0;   /* No layering needed */
+      if (cities[i].hits_left)
+        cities[i].img = IMG_CITY_BLUE;
+      else if (cities[i].status == CITY_EXPLODING)
+        cities[i].img = (IMG_CITY_BLUE_EXPL5 - (cities[i].counter / (CITY_EXPL_START / 5)));
+      else
+        cities[i].img = IMG_CITY_BLUE_DEAD;
+
+      /* Change image to appropriate color: */
+      cities[i].img = cities[i].img + ((wave % MAX_CITY_COLORS) *
+                   (IMG_CITY_GREEN - IMG_CITY_BLUE));
+
+    }
+  }
+}
+
+
+void game_handle_penguins(void)
+{
+  int i,direction,walk_counter;
+
+  if (!Opts_GetGlobalOpt(USE_IGLOOS))
+    return;
+  for (i = 0; i < NUM_CITIES; i++) {
+    penguins[i].layer = 0;
+    if (cities[i].status == CITY_EVAPORATING)
+      penguins[i].layer = 1;  /* will go higher in certain cases */
+    /* Handle interaction with comets & city status (ducking) */
+    if (cities[i].threatened && penguins[i].status < PENGUIN_WALKING_OFF
+        && penguins[i].status != PENGUIN_OFFSCREEN)
+      penguins[i].status = PENGUIN_DUCKING;
+    else if (!cities[i].threatened && penguins[i].status == PENGUIN_DUCKING) {
+      if (cities[i].hits_left == 2)
+        penguins[i].status = PENGUIN_HAPPY;
+      else
+        penguins[i].status = PENGUIN_GRUMPY;
+    }
+    switch (penguins[i].status) {
+    case PENGUIN_HAPPY:
+      penguins[i].img = IMG_PENGUIN_FLAPDOWN;
+      if (rand() % FLAPPING_INTERVAL == 0) {
+        penguins[i].status = PENGUIN_FLAPPING;
+        penguins[i].counter = FLAPPING_START;
+      }
+      break;
+    case PENGUIN_FLAPPING:
+      if (penguins[i].counter % 4 >= 2)
+        penguins[i].img = IMG_PENGUIN_FLAPUP;
+      else
+        penguins[i].img = IMG_PENGUIN_FLAPDOWN;
+      penguins[i].counter--;
+      if (penguins[i].counter == 0)
+        penguins[i].status = PENGUIN_HAPPY;
+      break;
+    case PENGUIN_DUCKING:
+      penguins[i].img = IMG_PENGUIN_INCOMING;
+      break;
+    case PENGUIN_GRUMPY:
+      penguins[i].img = IMG_PENGUIN_GRUMPY;
+      if (rand() % FLAPPING_INTERVAL == 0) {
+        penguins[i].status = PENGUIN_WORRIED;
+        penguins[i].counter = FLAPPING_START;
+      }
+      break;
+    case PENGUIN_WORRIED:
+      penguins[i].img = IMG_PENGUIN_WORRIED;
+      penguins[i].counter--;
+      if (penguins[i].counter == 0)
+        penguins[i].status = PENGUIN_GRUMPY;
+      break;
+    case PENGUIN_STANDING_UP:
+      penguins[i].img = IMG_PENGUIN_STANDING_UP;
+      penguins[i].counter--;
+      if (penguins[i].counter == 0)
+        penguins[i].status = PENGUIN_WALKING_OFF;
+      break;
+    case PENGUIN_SITTING_DOWN:
+      penguins[i].img = IMG_PENGUIN_SITTING_DOWN;
+      penguins[i].counter--;
+      if (penguins[i].counter == 0) {
+        penguins[i].status = PENGUIN_FLAPPING;
+        penguins[i].counter = FLAPPING_START;
+      }
+      break;
+    case PENGUIN_WALKING_ON:
+      walk_counter = (penguins[i].counter % 8)/2;
+      if (walk_counter == 3)
+        walk_counter = 1;
+      penguins[i].img = IMG_PENGUIN_WALK_ON1 + walk_counter;
+      penguins[i].counter++;
+      direction = 2*(i < NUM_CITIES/2)-1;  /* +1 for walk right, -1 for left */
+      penguins[i].x += direction*PENGUIN_WALK_SPEED;
+      if (direction*penguins[i].x >= direction*cities[i].x) {
+        penguins[i].status = PENGUIN_SITTING_DOWN;
+        penguins[i].counter = STANDING_COUNTER_START;
+        penguins[i].x = cities[i].x;
+      }
+      penguins[i].layer = 3;  /* Stand in front of steam */
+      break;
+    case PENGUIN_WALKING_OFF:
+      walk_counter = (penguins[i].counter % 8)/2;
+      if (walk_counter == 3)
+        walk_counter = 1;
+      penguins[i].img = IMG_PENGUIN_WALK_OFF1 + walk_counter;
+      penguins[i].counter++;
+      direction = 1-2*(i < NUM_CITIES/2);
+      penguins[i].x += direction*PENGUIN_WALK_SPEED;
+      if (direction < 0) {
+        if (penguins[i].x + images[IMG_PENGUIN_WALK_OFF1]->w/2 < 0)
+          penguins[i].status = PENGUIN_OFFSCREEN;
+      } else {
+        if (penguins[i].x - images[IMG_PENGUIN_WALK_OFF1]->w/2 > screen->w)
+          penguins[i].status = PENGUIN_OFFSCREEN;
+      }
+      penguins[i].layer = 3;
+      break;
+    case PENGUIN_OFFSCREEN:
+      penguins[i].img = -1;
+      break;
+    }
+  }
+}
+
+void game_handle_steam(void)
+{
+  int i;
+
+  if (!Opts_GetGlobalOpt(USE_IGLOOS))
+    return;
+  for (i = 0; i < NUM_CITIES; i++) {
+    if (steam[i].counter) {
+      steam[i].counter--;
+      if (!steam[i].counter) {
+        steam[i].status = STEAM_OFF;
+        if (cloud.status != EXTRA_LIFE_ON || cloud.city != i) {
+          /* The penguin was ducking, now we can stop */
+          if (cities[i].hits_left)
+            penguins[i].status = PENGUIN_GRUMPY;
+          else {
+            penguins[i].status = PENGUIN_STANDING_UP;
+            penguins[i].counter = STANDING_COUNTER_START;
+          }
+        }
+      }
+    }
+    if (steam[i].status == STEAM_OFF)
+      steam[i].img = -1;
+    else {
+      steam[i].img = IMG_STEAM5 - steam[i].counter/3;
+      steam[i].layer = 2;
+    }
+  }
+}
+
+int check_extra_life(void)
+{
+  /* This is called at the end of a wave. Returns 1 if we're in the
+     middle of handling an extra life, otherwise 0 */
+  int i,fewest_hits_left,fewest_index,snow_width;
+
+  if (cloud.status == EXTRA_LIFE_ON)
+    return 1;
+#ifdef TUXMATH_DEBUG
+  print_status();
+#endif
+  if (extra_life_earned) {
+    /* Check to see if any ingloo has been hit */
+    fewest_hits_left = 2;
+    fewest_index = -1;
+    for (i = 0; i < NUM_CITIES; i++) {
+      if (cities[i].hits_left < fewest_hits_left) {
+        fewest_hits_left = cities[i].hits_left;
+        fewest_index = i;
+      }
+    }
+    if (fewest_hits_left == 2)
+      return 0;   /* Don't need an extra life, there's no damage */
+    /* Begin the extra life sequence */
+    extra_life_earned = 0;
+    cloud.status = EXTRA_LIFE_ON;
+    cloud.y = screen->h/3;
+    cloud.city = fewest_index;
+    bonus_comet_counter = Opts_BonusCometInterval()+1;
+#ifdef TUXMATH_DEBUG
+    printf("\nBonus comet counter restored to %d\n",bonus_comet_counter);
+#endif
+    if (cloud.city < NUM_CITIES/2)
+      cloud.x = -images[IMG_CLOUD]->w/2;  /* come in from the left */
+    else
+      cloud.x = screen->w + images[IMG_CLOUD]->w/2; /* come from the right */
+    penguins[cloud.city].status = PENGUIN_WALKING_ON;
+    /* initialize the snowflakes */
+    snow_width = images[IMG_CLOUD]->w - images[IMG_SNOW1]->w;
+    for (i = 0; i < NUM_SNOWFLAKES; i++) {
+      cloud.snowflake_y[i] = cloud.y - i*SNOWFLAKE_SEPARATION;
+      cloud.snowflake_x[i] = - snow_width/2  + (rand() % snow_width);
+      cloud.snowflake_size[i] = rand() % 3;
+    }
+#ifdef TUXMATH_DEBUG
+    print_status();
+#endif
+    return 1;
+  } else
+    return 0;
+}
+
+void game_handle_extra_life(void)
+{
+  // This handles the animation sequence during the rebuilding of an igloo
+  int i,igloo_top,num_below_igloo,direction;
+
+  if (cloud.status == EXTRA_LIFE_ON) {
+
+#ifdef TUXMATH_DEBUG
+     if (penguins[cloud.city].status == PENGUIN_WALKING_OFF) {
+       print_status();
+       pause_game();
+     }
+#endif
+
+    // Get the cloud moving in the right direction, if not yet "parked"
+    direction = 2*(cloud.city < NUM_CITIES/2) - 1;
+    if (direction*cloud.x < direction*cities[cloud.city].x) {
+      cloud.x += direction*PENGUIN_WALK_SPEED;
+    }
+    else {
+      // Cloud is "parked," handle the snowfall and igloo rebuilding
+      cities[cloud.city].status = CITY_REBUILDING;
+      igloo_top = screen->h - igloo_vertical_offset
+        - images[IMG_IGLOO_INTACT]->h;
+      for (i = 0, num_below_igloo = 0; i < NUM_SNOWFLAKES; i++) {
+        cloud.snowflake_y[i] += SNOWFLAKE_SPEED;
+        if (cloud.snowflake_y[i] > igloo_top)
+          num_below_igloo++;
+      }
+      if (cloud.snowflake_y[NUM_SNOWFLAKES-1] > igloo_top) {
+        cities[cloud.city].hits_left = 2;
+        cities[cloud.city].img = IMG_IGLOO_INTACT; // completely rebuilt
+      } else if (cities[cloud.city].hits_left == 0) {
+        // We're going to draw one of the blended igloos
+        // FIXME: It's a hack to encode a blended igloo with a negative number!
+        penguins[cloud.city].layer = 0;
+        cities[cloud.city].layer = 1;
+        if (num_below_igloo < 3)
+          num_below_igloo = 0;   // Don't show progress until a few have fallen
+        cities[cloud.city].img = -((float) (num_below_igloo)/NUM_SNOWFLAKES) * NUM_BLENDED_IGLOOS;
+      }
+      if (cloud.snowflake_y[NUM_SNOWFLAKES-1] > screen->h - igloo_vertical_offset) {
+        /* exit rebuilding when last snowflake at igloo bottom */
+        cloud.status = EXTRA_LIFE_OFF;
+        cities[cloud.city].status = CITY_PRESENT;
+      }
+    }
+  }
+}
+
+void game_draw(void)
+{
+  SDL_Rect dest;
+
+  /* Clear screen: */
+  game_draw_background();
+
+  /* Draw miscellaneous informational items */
+  game_draw_misc();
+
+  /* Draw cities/igloos and (if applicable) penguins: */
+  game_draw_cities();
+
+  /* Draw normal comets first, then bonus comets */
+  game_draw_comets();
+
+
+  /* Draw laser: */
+  if (laser.alive)
+  {
+    draw_line(laser.x1, laser.y1, laser.x2, laser.y2,
+                  255 / ((LASER_START + 1) - laser.alive),
+                  192 / ((LASER_START + 1) - laser.alive),
+                  64);
+  }
+
+  /* Draw numeric keypad: */
+  if (Opts_GetGlobalOpt(USE_KEYPAD))
+  {
+    /* pick image to draw: */
+    int keypad_image;
+    if (MC_GetOpt(ALLOW_NEGATIVES) )
+    {
+      /* draw regular keypad */
+      keypad_image = IMG_KEYPAD;
+    }
+    else
+    {
+      /* draw keypad with with grayed-out '+' and '-' */
+      keypad_image = IMG_KEYPAD_NO_NEG;
+    }
+
+    /* now draw it: */
+    dest.x = (screen->w - images[keypad_image]->w) / 2;
+    dest.y = (screen->h - images[keypad_image]->h) / 2;
+    dest.w = images[keypad_image]->w;
+    dest.h = images[keypad_image]->h;
+    SDL_BlitSurface(images[keypad_image], NULL, screen, &dest);
+  }
+
+  /* Draw console, LED numbers, & tux: */
+  draw_led_console();
+  draw_console_image(tux_img);
+
+  /* Draw any messages on the screen (used for the help mode) */
+  game_write_messages();
+
+  /* Swap buffers: */
+  SDL_Flip(screen);
+}
+
+void game_draw_background(void)
+{
+  static int old_wave = 0; //update wave immediately
+  static Uint32 bgcolor, fgcolor = 0;
+  SDL_Rect dest;
+
+  if (fgcolor == 0)
+    fgcolor = SDL_MapRGB(screen->format, 64, 96, 64);
+  if (old_wave != wave)
+  {
+    tmdprintf("Wave %d\n", wave);
+    old_wave = wave;
+    bgcolor = SDL_MapRGB(screen->format,
+                         64,
+                         64 + ((wave * 32) % 192),
+                         128 - ((wave * 16) % 128) );
+    tmdprintf("Filling screen with color %d\n", bgcolor);
+  }
+
+  if (current_bkgd() == NULL || (current_bkgd()->w != screen->w && 
+                                 current_bkgd()->h != screen->h) )
+  {
+    dest.x = 0;
+    dest.y = 0;
+    dest.w = screen->w;
+    dest.h = ((screen->h) / 4) * 3;
+
+    SDL_FillRect(screen, &dest, bgcolor);
+
+
+    dest.y = ((screen->h) / 4) * 3;
+    dest.h = (screen->h) / 4;
+
+    SDL_FillRect(screen, &dest, fgcolor);
+  }
+
+  if (current_bkgd())
+  {
+    dest.x = (screen->w - current_bkgd()->w) / 2;
+    dest.y = (screen->h - current_bkgd()->h) / 2;
+    SDL_BlitSurface(current_bkgd(), NULL, screen, &dest);
+  }
+}
+
+/* Draw comets: */
+/* NOTE bonus comets split into separate pass to make them */
+/* draw last (i.e. in front), as they can overlap          */
+void game_draw_comets(void)
+{
+
+  int i, img;
+  SDL_Rect dest;
+  char* comet_str;
+
+   /* First draw regular comets: */
+  for (i = 0; i < MAX_COMETS; i++)
+  {
+    if (comets[i].alive && !comets[i].bonus)
+    {
+      if (comets[i].expl < COMET_EXPL_END)
+      {
+        /* Decide which image to display: */
+        img = IMG_COMET1 + ((frame + i) % 3);
+        /* Display the formula (flashing, in the bottom half
+                   of the screen) */
+        if (comets[i].y < screen->h / 2 || frame % 8 < 6)
+        {
+          comet_str = comets[i].flashcard.formula_string;
+        }
+        else
+        {
+          comet_str = NULL;
+        }
+      }
+      else
+      {
+        img = comets[i].expl;
+        comet_str = comets[i].flashcard.answer_string;
+      }
+
+      /* Draw it! */
+      dest.x = comets[i].x - (images[img]->w / 2);
+      dest.y = comets[i].y - images[img]->h;
+      dest.w = images[img]->w;
+      dest.h = images[img]->h;
+
+      SDL_BlitSurface(images[img], NULL, screen, &dest);
+      if (comet_str != NULL)
+      {
+        draw_nums(comet_str, comets[i].x, comets[i].y);
+      }
+    }
+  }
+
+  /* Now draw any bonus comets: */
+  for (i = 0; i < MAX_COMETS; i++)
+  {
+    if (comets[i].alive && comets[i].bonus)
+    {
+      if (comets[i].expl < COMET_EXPL_END)
+      {
+        /* Decide which image to display: */
+        img = IMG_COMET1 + ((frame + i) % 3);
+        /* Display the formula (flashing, in the bottom half
+                   of the screen) */
+        if (comets[i].y < screen->h / 2 || frame % 8 < 6)
+        {
+          comet_str = comets[i].flashcard.formula_string;
+        }
+        else
+        {
+          comet_str = NULL;
+        }
+      }
+      else
+      {
+        img = comets[i].expl;
+        comet_str = comets[i].flashcard.answer_string;
+      }
+
+      /* Move images[] index to bonus range: */
+      img += IMG_BONUS_COMET1 - IMG_COMET1;
+
+      /* Draw it! */
+      dest.x = comets[i].x - (images[img]->w / 2);
+      dest.y = comets[i].y - images[img]->h;
+      dest.w = images[img]->w;
+      dest.h = images[img]->h;
+
+      SDL_BlitSurface(images[img], NULL, screen, &dest);
+      if (comet_str != NULL)
+      {
+        draw_nums(comet_str, comets[i].x, comets[i].y);
+      }
+    }
+  }
+
+
+
+}
+
+void game_draw_cities(void)
+{
+  int i, j, current_layer, max_layer;
+  SDL_Rect src, dest;
+  SDL_Surface* this_image;
+
+  if (Opts_GetGlobalOpt(USE_IGLOOS)) {
+    /* We have to draw respecting layering */
+    current_layer = 0;
+    max_layer = 0;
+    do {
+      for (i = 0; i < NUM_CITIES; i++) {
+        if (cities[i].status != CITY_GONE && cities[i].layer > max_layer)
+          max_layer = cities[i].layer;
+        if (penguins[i].status != PENGUIN_OFFSCREEN && penguins[i].layer > max_layer)
+          max_layer = penguins[i].layer;
+        if (steam[i].status == STEAM_ON && steam[i].layer > max_layer)
+          max_layer = steam[i].layer;
+        if (cities[i].layer == current_layer &&
+            cities[i].img != IMG_CITY_NONE) {
+          // Handle the blended igloo images, which are encoded
+          // (FIXME) with a negative image number
+          if (cities[i].img <= 0)
+            this_image = blended_igloos[-cities[i].img];
+          else
+            this_image = images[cities[i].img];
+          //this_image = blended_igloos[frame % NUM_BLENDED_IGLOOS];
+          dest.x = cities[i].x - (this_image->w / 2);
+          dest.y = (screen->h) - (this_image->h) - igloo_vertical_offset;
+          if (cities[i].img == IMG_IGLOO_MELTED3 ||
+              cities[i].img == IMG_IGLOO_MELTED2)
+            dest.y -= (images[IMG_IGLOO_MELTED1]->h - this_image->h)/2;
+          dest.w = (this_image->w);
+          dest.h = (this_image->h);
+          SDL_BlitSurface(this_image, NULL, screen, &dest);
+        }
+        if (penguins[i].layer == current_layer &&
+            penguins[i].status != PENGUIN_OFFSCREEN) {
+          this_image = images[penguins[i].img];
+          if (penguins[i].status == PENGUIN_WALKING_OFF ||
+              penguins[i].status == PENGUIN_WALKING_ON) {
+            /* With walking penguins, we have to use flipped images
+               when it's walking left. The other issue is that the
+               images are of different widths, so aligning on the
+               center produces weird forward-backward walking. The
+               reliable way is the align them all on the tip of the
+               beak (the right border of the unflipped image) */
+            dest.x = penguins[i].x - (this_image->w / 2);
+            dest.y = (screen->h) - (this_image->h);
+            if ((i<NUM_CITIES/2 && penguins[i].status==PENGUIN_WALKING_OFF) ||
+                (i>=NUM_CITIES/2 && penguins[i].status==PENGUIN_WALKING_ON)) {
+              /* walking left */
+              this_image = flipped_images[flipped_img_lookup[penguins[i].img]];
+              dest.x = penguins[i].x - images[IMG_PENGUIN_WALK_OFF2]->w/2;
+            } else
+              dest.x = penguins[i].x - this_image->w
+                + images[IMG_PENGUIN_WALK_OFF2]->w/2;   /* walking right */
+          }
+          else {
+            dest.x = penguins[i].x - (this_image->w / 2);
+            dest.y = (screen->h) - (5*(this_image->h))/4 - igloo_vertical_offset;
+          }
+          dest.w = (this_image->w);
+          dest.h = (this_image->h);
+          SDL_BlitSurface(this_image, NULL, screen, &dest);
+        }
+        if (steam[i].layer == current_layer &&
+            steam[i].status == STEAM_ON) {
+          this_image = images[steam[i].img];
+          dest.x = cities[i].x - (this_image->w / 2);
+          dest.y = (screen->h) - this_image->h - ((4 * images[IMG_IGLOO_INTACT]->h) / 7);
+          dest.w = (this_image->w);
+          dest.h = (this_image->h);
+          SDL_BlitSurface(this_image, NULL, screen, &dest);
+        }
+      }
+      current_layer++;
+    } while (current_layer <= max_layer);
+    if (cloud.status == EXTRA_LIFE_ON) {
+      /* Render cloud & snowflakes */
+      for (i = 0; i < NUM_SNOWFLAKES; i++) {
+        if (cloud.snowflake_y[i] > cloud.y &&
+            cloud.snowflake_y[i] < screen->h - igloo_vertical_offset) {
+          this_image = images[IMG_SNOW1+cloud.snowflake_size[i]];
+          dest.x = cloud.snowflake_x[i] - this_image->w/2 + cloud.x;
+          dest.y = cloud.snowflake_y[i] - this_image->h/2;
+          dest.w = this_image->w;
+          dest.h = this_image->h;
+          SDL_BlitSurface(this_image, NULL, screen, &dest);
+        }
+      }
+      this_image = images[IMG_CLOUD];
+      dest.x = cloud.x - this_image->w/2;
+      dest.y = cloud.y - this_image->h/2;
+      dest.w = this_image->w;
+      dest.h = this_image->h;
+      SDL_BlitSurface(this_image, NULL, screen, &dest);
+    }
+  }
+  else {
+    /* We're drawing original city graphics, for which there are no
+       layering issues, but has special handling for the shields */
+    for (i = 0; i < NUM_CITIES; i++) {
+      this_image = images[cities[i].img];
+      dest.x = cities[i].x - (this_image->w / 2);
+      dest.y = (screen->h) - (this_image->h);
+      dest.w = (this_image->w);
+      dest.h = (this_image->h);
+      SDL_BlitSurface(this_image, NULL, screen, &dest);
+
+      /* Draw sheilds: */
+      if (cities[i].hits_left > 1) {
+        for (j = (frame % 3); j < images[IMG_SHIELDS]->h; j = j + 3) {
+          src.x = 0;
+          src.y = j;
+          src.w = images[IMG_SHIELDS]->w;
+          src.h = 1;
+
+          dest.x = cities[i].x - (images[IMG_SHIELDS]->w / 2);
+          dest.y = (screen->h) - (images[IMG_SHIELDS]->h) + j;
+          dest.w = src.w;
+          dest.h = src.h;
+
+          SDL_BlitSurface(images[IMG_SHIELDS], &src, screen, &dest);
+        }
+      }
+    }
+  }
+
+
+}
+void game_draw_misc(void)
+{
+  int i;
+  int offset;
+  SDL_Rect dest;
+  char str[64];
+
+  /* Draw "Demo" */
+  if (Opts_DemoMode())
+  {
+    dest.x = (screen->w - images[IMG_DEMO]->w) / 2;
+    dest.y = (screen->h - images[IMG_DEMO]->h) / 2;
+    dest.w = images[IMG_DEMO]->w;
+    dest.h = images[IMG_DEMO]->h;
+
+    SDL_BlitSurface(images[IMG_DEMO], NULL, screen, &dest);
+  }
+
+  /* If we are playing through a defined list of questions */
+  /* without "recycling", display number of remaining questions: */
+  if (MC_GetOpt(PLAY_THROUGH_LIST) )
+  {
+    draw_question_counter();
+  }
+
+  if (extra_life_earned) {
+    /* Draw extra life earned icon */
+    dest.x = 0;
+    dest.y = 0;
+    dest.w = images[IMG_EXTRA_LIFE]->w;
+    dest.h = images[IMG_EXTRA_LIFE]->h;
+    SDL_BlitSurface(images[IMG_EXTRA_LIFE], NULL, screen, &dest);
+  } else if (bonus_comet_counter) {
+    /* Draw extra life progress bar */
+    dest.x = 0;
+    dest.y = images[IMG_EXTRA_LIFE]->h/4;
+    dest.h = images[IMG_EXTRA_LIFE]->h/2;
+    dest.w = ((Opts_BonusCometInterval() + 1 - bonus_comet_counter)
+              * images[IMG_EXTRA_LIFE]->w) / Opts_BonusCometInterval();
+    SDL_FillRect(screen, &dest, SDL_MapRGB(screen->format, 0, 255, 0));
+  }
+
+  /* Draw wave: */
+  if (Opts_BonusCometInterval())
+    offset = images[IMG_EXTRA_LIFE]->w + 5;
+  else
+    offset = 0;
+
+  dest.x = offset;
+  dest.y = glyph_offset;
+  dest.w = images[IMG_WAVE]->w;
+  dest.h = images[IMG_WAVE]->h;
+
+  SDL_BlitSurface(images[IMG_WAVE], NULL, screen, &dest);
+
+  sprintf(str, "%d", wave);
+  draw_numbers(str, offset+images[IMG_WAVE]->w + (images[IMG_NUMBERS]->w / 10), 0);
+
+  if (Opts_KeepScore() )
+  {
+    /* Draw "score" label: */
+    dest.x = (screen->w - ((images[IMG_NUMBERS]->w / 10) * 7) -
+                  images[IMG_SCORE]->w -
+                  images[IMG_STOP]->w - 5);
+    dest.y = glyph_offset;
+    dest.w = images[IMG_SCORE]->w;
+    dest.h = images[IMG_SCORE]->h;
+    SDL_BlitSurface(images[IMG_SCORE], NULL, screen, &dest);
+  
+    /* Draw score numbers: */
+    sprintf(str, "%.6d", score);
+    draw_numbers(str,
+                 screen->w - ((images[IMG_NUMBERS]->w / 10) * 6) - images[IMG_STOP]->w - 5,
+                 0);
+  }
+  
+  /* Draw other players' scores */
+  if (mp_get_parameter(PLAYERS) && mp_get_parameter(MODE) == SCORE_SWEEP )
+  {
+    for (i = 0; i < mp_get_parameter(PLAYERS); ++i)
+    {
+      snprintf(str, 64, "%s: %d", mp_get_player_name(i),mp_get_player_score(i));
+      SDL_Surface* score = BlackOutline(str, default_font, &white);
+      SDL_Rect loc = {screen->w - score->w, score->h * (i + 2), 0, 0};
+      SDL_BlitSurface(score, NULL, screen, &loc);
+    }
+  }
+  
+  /* Draw stop button: */
+  if (!help_controls.x_is_blinking || (frame % 10 < 5)) {
+    dest.x = (screen->w - images[IMG_STOP]->w);
+    dest.y = 0;
+    dest.w = images[IMG_STOP]->w;
+    dest.h = images[IMG_STOP]->h;
+
+    SDL_BlitSurface(images[IMG_STOP], NULL, screen, &dest);
+  }
+}
+
+int check_exit_conditions(void)
+{
+  if (SDL_quit_received)
+  {
+    return GAME_OVER_WINDOW_CLOSE;
+  }
+
+  if (escape_received)
+  {
+    return GAME_OVER_ESCAPE;
+  }
+
+  /* determine if game lost (i.e. all cities blown up): */
+  if (!num_cities_alive)
+  {
+    if (gameover_counter < 0)
+      gameover_counter = GAMEOVER_COUNTER_START;
+    gameover_counter--;
+    if (gameover_counter == 0)
+      return GAME_OVER_LOST;
+  }
+
+  /* determine if game won (i.e. all questions in mission answered correctly): */
+  if (MC_MissionAccomplished())
+  {
+    tmdprintf("Mission accomplished!\n");
+    return GAME_OVER_WON;
+  }
+
+  /* Could have situation where mathcards doesn't have more questions */
+  /* even though not all questions answered correctly:                */
+  if (!MC_TotalQuestionsLeft())
+  {
+    return GAME_OVER_OTHER;
+  }
+
+  /* Need to get out if no comets alive and MathCards has no questions left in list, */
+  /* even though MathCards thinks there are still questions "in play".  */
+  /* This SHOULD NOT HAPPEN and means we have a bug somewhere. */
+  if (!MC_ListQuestionsLeft() && !num_comets_alive)
+  {
+    #ifdef TUXMATH_DEBUG
+    printf("\nListQuestionsLeft() = %d", MC_ListQuestionsLeft());
+    printf("\nnum_comets_alive = %d", num_comets_alive);
+    #endif
+    return GAME_OVER_ERROR;
+  }
+
+  /* If using demo mode, see if counter has run out: */
+  if (Opts_DemoMode())
+  {
+    if (demo_countdown <= 0 )
+      return GAME_OVER_OTHER;
+  }
+
+  /* if we made it to here, the game goes on! */
+  return GAME_IN_PROGRESS;
+}
+
+#ifdef TUXMATH_DEBUG
+void print_exit_conditions(void)
+{
+  printf("\ngame_status:\t");
+  switch (game_status)
+  {
+    case GAME_IN_PROGRESS:
+    {
+      printf("GAME_IN_PROGRESS\n");
+      break;
+    }
+
+    case GAME_OVER_WON:
+    {
+      printf("GAME_OVER_WON\n");
+      break;
+    }
+    case GAME_OVER_LOST:
+    {
+      printf("GAME_OVER_LOST\n");
+      break;
+    }
+    case GAME_OVER_OTHER:
+    {
+      printf("GAME_OVER_OTHER\n");
+      break;
+    }
+    case GAME_OVER_ESCAPE:
+    {
+      printf("GAME_OVER_ESCAPE\n");
+      print_status();
+      break;
+    }
+    case GAME_OVER_WINDOW_CLOSE:
+    {
+      printf("GAME_OVER_WINDOW_CLOSE\n");
+      break;
+    }
+    case GAME_OVER_ERROR:
+    {
+      printf("GAME_OVER_ERROR\n");
+      break;
+    }
+    default:
+    {
+      printf("Unrecognized value\n");
+      break;
+    }
+  }
+}
+#endif
+
+/* Reset stuff for the next level! */
+void reset_level(void)
+{
+  char fname[1024];
+  int i;
+  int next_wave_comets;
+  int use_feedback;
+  float comet_avg_height,height_differential;
+
+
+  /* Clear all comets: */
+
+  for (i = 0; i < MAX_COMETS; i++)
+  {
+    comets[i].alive = 0;
+  }
+  num_comets_alive = 0;
+
+  /* Clear LED F: */
+
+  for (i = 0; i < MC_MAX_DIGITS; ++i)
+    digits[i] = 0;
+  neg_answer_picked = 0;
+
+
+
+  /* Load random background image, but ensure it's different from this one: */
+  for (i = last_bkgd; i == last_bkgd; i = rand() % NUM_BKGDS);
+
+  last_bkgd = i;
+
+  sprintf(fname, "backgrounds/%d.jpg", i);
+
+  if (bkgd != NULL)
+  {
+    SDL_FreeSurface(bkgd);
+    bkgd = NULL;
+  }
+  if (scaled_bkgd != NULL)
+  {
+    SDL_FreeSurface(scaled_bkgd);
+    scaled_bkgd = NULL;
+  }
+
+  if (Opts_UseBkgd())
+  {
+    LoadBothBkgds(fname, &scaled_bkgd, &bkgd);
+    if (bkgd == NULL || scaled_bkgd == NULL)
+    {
+      fprintf(stderr,
+              "\nWarning: Could not load background image:\n"
+              "%s\n"
+              "The Simple DirectMedia error that ocurred was: %s\n",
+              fname, SDL_GetError());
+      Opts_SetUseBkgd(0);
+    }
+  }
+
+
+
+  /* Record score before this wave: */
+
+  pre_wave_score = score;
+
+  /* Set number of attackers for this wave: */
+
+  /* On first wave or if slowdown flagged due to wrong answer: */
+  if (wave == 1 || slowdown)
+  {
+    next_wave_comets = Opts_StartingComets();
+    speed = Opts_Speed();
+    slowdown = 0;
+  }
+
+  else /* Otherwise increase comets and speed if selected, not to */
+       /* exceed maximum:                                         */
+  {
+    next_wave_comets = prev_wave_comets;
+    if (Opts_AllowSpeedup())
+    {
+      next_wave_comets += Opts_ExtraCometsPerWave();
+      if (next_wave_comets > Opts_MaxComets())
+      {
+        next_wave_comets = Opts_MaxComets();
+      }
+
+      use_feedback = Opts_UseFeedback();
+
+      if (use_feedback)
+      {
+        #ifdef FEEDBACK_DEBUG
+        printf("Evaluating feedback...\n  old danger level = %g,",danger_level);
+        #endif
+
+        /* Update our danger level, i.e., the target height */
+        danger_level = 1 - (1-danger_level) /
+                           Opts_DangerLevelSpeedup();
+        if (danger_level > Opts_DangerLevelMax())
+          danger_level = Opts_DangerLevelMax();
+
+        #ifdef FEEDBACK_DEBUG
+        printf(" new danger level = %g.\n",danger_level);
+        #endif
+
+        /* Check to see whether we have any feedback data. If not, skip it. */
+        if (comet_feedback_number == 0)
+        {
+          use_feedback = 0;  /* No comets above living cities, skip feedback */
+
+          #ifdef FEEDBACK_DEBUG
+          printf("No feedback data available, aborting.\n\n");
+          #endif
+        }
+        else
+        {
+          /* Compute the average height of comet destruction. */
+          comet_avg_height = comet_feedback_height/comet_feedback_number;
+
+          /* Determine how this average height compares with target. */
+          height_differential = comet_avg_height - danger_level;
+
+          /* Set the speed so that we move halfway towards the target */
+          /* height. That makes the changes a bit more conservative. */
+
+          #ifdef FEEDBACK_DEBUG
+          printf("  comet average height = %g, height differential = %g.\n",
+                 comet_avg_height, height_differential);
+          printf("  old speed = %g,",speed);
+          #endif
+
+          speed *= (1 - height_differential/danger_level/2);
+
+          /* Enforce bounds on speed */
+          if (speed < MINIMUM_SPEED)
+            speed = MINIMUM_SPEED;
+          if (speed > Opts_MaxSpeed())
+            speed = Opts_MaxSpeed();
+
+          #ifdef FEEDBACK_DEBUG
+          printf(" new speed = %g.\n",speed);
+          printf("Feedback evaluation complete.\n\n");
+          #endif
+        }
+      }
+
+      if (!use_feedback)
+      {
+        /* This is not an "else" because we might skip feedback */
+        /* when comet_feedback_number == 0 */
+        speed = speed * Opts_SpeedupFactor();
+        if (speed > Opts_MaxSpeed())
+        {
+          speed = Opts_MaxSpeed();
+        }
+      }
+    }
+  }
+
+  comet_feedback_number = 0;
+  comet_feedback_height = 0;
+
+  prev_wave_comets = next_wave_comets;
+  num_attackers = prev_wave_comets;
+}
+
+
+
+/* Add a comet to the game (if there's room): */
+int add_comet(void)
+{
+  static int prev_city = -1;
+  int i, found;
+  float y_spacing;
+
+  /* Look for a free comet slot and see if all live comets are far */
+  /* enough down to avoid overlap and keep formulas legible:       */
+  found = -1;
+  y_spacing = (images[IMG_NUMS]->h) * 1.5;
+
+  for (i = 0; i < MAX_COMETS && found == -1; i++)
+  {
+    if (comets[i].alive)
+    {
+      if (comets[i].y < y_spacing)
+      {
+        /* previous comet too high up to create another one yet: */
+        return 0;
+      }
+    }
+    else  /* non-living comet so we found a free slot: */
+    {
+      found = i;
+    }
+  }
+
+  if (-1 == found)
+  {
+    /* free comet slot not found - no comet added: */
+    return 0;
+  }
+
+
+  /* Get math question for new comet - the following function fills in */
+  /* the flashcard struct that is part of the comet struct:            */
+  if (!MC_NextQuestion(&(comets[found].flashcard)))
+  {
+    /* no more questions available - cannot create comet.  */
+    return 0;
+  }
+
+  /* If we make it to here, create a new comet!                  */
+
+  comets[found].answer = comets[found].flashcard.answer;
+//  /* The answer may be num1, num2, or num3, depending on format. */
+//  switch (comets[found].flashcard.format)
+//  {
+//    case MC_FORMAT_ANS_LAST:  /* e.g. num1 + num2 = ? */
+//    {
+//      comets[found].answer = comets[found].flashcard.num3;
+//      break;
+//    }
+//    case MC_FORMAT_ANS_MIDDLE:  /* e.g. num1 + ? = num3 */
+//    {
+//      comets[found].answer = comets[found].flashcard.num2;
+//      break;
+//    }
+//    case MC_FORMAT_ANS_FIRST:  /* e.g. ? + num2 = num3 */
+//    {
+//      comets[found].answer = comets[found].flashcard.num1;
+//      break;
+//    }
+//    default:  /* should not get to here if MathCards behaves correctly */
+//    {
+//      fprintf(stderr, "\nadd_comet() - invalid question format");
+//      return 0;
+//    }
+//  }
+  
+
+  comets[found].alive = 1;
+  num_comets_alive++;
+
+  /* Pick a city to attack that was not attacked last time */
+  /* (so formulas are less likely to overlap). */
+  do
+  {
+    i = rand() % NUM_CITIES;
+  }
+  while (i == prev_city);
+
+  prev_city = i;
+
+  /* Set in to attack that city: */
+  comets[found].city = i;
+  /* Start at the top, above the city in question: */
+  comets[found].x = cities[i].x;
+  comets[found].y = 0;
+  comets[found].zapped = 0;
+  /* Should it be a bonus comet? */
+  comets[found].bonus = 0;
+#ifdef TUXMATH_DEBUG
+  printf("\nbonus_comet_counter is %d\n",bonus_comet_counter);
+#endif
+  if (bonus_comet_counter == 1) {
+    bonus_comet_counter = 0;
+    comets[found].bonus = 1;
+    playsound(SND_BONUS_COMET);
+#ifdef TUXMATH_DEBUG
+    printf("\nCreated bonus comet");
+#endif
+  }
+
+  #ifdef TUXMATH_DEBUG
+  printf ("\nadd_comet(): formula string is: %s", comets[found].flashcard.formula_string);
+  #endif
+
+  /* Record the time at which this comet was created */
+  comets[found].time_started = SDL_GetTicks();
+
+  /* comet slot found and question found so return successfully: */
+  return 1;
+}
+
+/* Draw numbers/symbols over the attacker: */
+/* This draws the numbers related to the comets */
+void draw_nums(const char* str, int x, int y)
+{
+  int i, j, cur_x, c;
+  int str_length, char_width, image_length;
+
+  SDL_Rect src, dest;
+
+  /* avoid some recalculation and repeated function calls: */
+  str_length = strlen(str);
+  /* IMG_NUMS now consists of 10 digit graphics, NUM_OPERS (i.e. 4) */
+  /* operation symbols, and the '=' and '?' symbols, all side by side. */
+  /* char_width is the width of a single symbol.                     */
+  char_width = (images[IMG_NUMS]->w / (16));
+  /* Calculate image_length, taking into account that the string will */
+  /* usually have four empty spaces that are only half as wide:       */
+  image_length = str_length * char_width - (char_width * 0.5 * 4);
+  /* Center around the shape */
+  cur_x = x - (image_length) / 2;
+
+  /* the following code keeps the formula at least 8 pixels inside the window: */
+  if (cur_x < 8)
+    cur_x = 8;
+  if (cur_x + (image_length) >=
+      (screen->w - 8))
+    cur_x = ((screen->w - 8) -
+             (image_length));
+
+
+  /* Draw each character: */
+
+  for (i = 0; i < str_length; i++)
+  {
+    c = -1;
+
+
+    /* Determine which character to display: */
+
+    if (str[i] >= '0' && str[i] <= '9')
+    {
+      c = str[i] - '0';
+    }
+    else if ('=' == str[i])
+    {
+      c = 14;  /* determined by layout of nums.png image */
+    }
+    else if ('?' == str[i])
+    {
+      c = 15;  /* determined by layout of nums.png image */
+    }
+    else  /* [ THIS COULD CAUSE SLOWNESS... ] */
+    {
+      for (j = 0; j < 4; j++)
+      {
+        if (str[i] == operchars[j])
+        {
+          c = 10 + j;
+        }
+      }
+    }
+
+    /* Display this character! */
+    if (c != -1)
+    {
+      src.x = c * char_width;
+      src.y = 0;
+      src.w = char_width;
+      src.h = images[IMG_NUMS]->h;
+
+      dest.x = cur_x;
+      dest.y = y - images[IMG_NUMS]->h;
+      dest.w = src.w;
+      dest.h = src.h;
+
+      SDL_BlitSurface(images[IMG_NUMS], &src,
+                          screen, &dest);
+      /* Move the 'cursor' one character width: */
+      cur_x = cur_x + char_width;
+    }
+    /* If char is a blank, no drawing to do but still move the cursor: */
+    /* NOTE: making spaces only half as wide seems to look better.     */
+    if (' ' == str[i])
+    {
+      cur_x = cur_x + (char_width * 0.5);
+    }
+  }
+}
+
+
+/* Draw status numbers: */
+void draw_numbers(const char* str, int x, int y)
+{
+  int i, cur_x, c;
+  SDL_Rect src, dest;
+
+
+  cur_x = x;
+
+
+  /* Draw each character: */
+
+  for (i = 0; i < strlen(str); i++)
+    {
+      c = -1;
+
+
+      /* Determine which character to display: */
+
+      if (str[i] >= '0' && str[i] <= '9')
+        c = str[i] - '0';
+
+
+      /* Display this character! */
+
+      if (c != -1)
+        {
+          src.x = c * (images[IMG_NUMBERS]->w / 10);
+          src.y = 0;
+          src.w = (images[IMG_NUMBERS]->w / 10);
+          src.h = images[IMG_NUMBERS]->h;
+
+          dest.x = cur_x;
+          dest.y = y;
+          dest.w = src.w;
+          dest.h = src.h;
+
+          SDL_BlitSurface(images[IMG_NUMBERS], &src,
+                          screen, &dest);
+
+
+          /* Move the 'cursor' one character width: */
+
+          cur_x = cur_x + (images[IMG_NUMBERS]->w / 10);
+        }
+    }
+}
+
+
+/* Pause loop: */
+
+int pause_game(void)
+{
+  /* NOTE - done and quit changed to pause_done and pause_quit */
+  /* due to potentially confusing name collision */
+  int pause_done, pause_quit;
+  SDL_Event event;
+  SDL_Rect dest;
+
+  /* Only pause if pause allowed: */
+  if (!Opts_AllowPause())
+  {
+    fprintf(stderr, "Pause requested but not allowed by Opts!\n");
+    return 0;
+  }
+
+  pause_done = 0;
+  pause_quit = 0;
+
+  dest.x = (screen->w - images[IMG_PAUSED]->w) / 2;
+  dest.y = (screen->h - images[IMG_PAUSED]->h) / 2;
+  dest.w = images[IMG_PAUSED]->w;
+  dest.h = images[IMG_PAUSED]->h;
+
+  DarkenScreen(1);  // cut all channels by half
+  SDL_BlitSurface(images[IMG_PAUSED], NULL, screen, &dest);
+  SDL_UpdateRect(screen, 0, 0, 0, 0);
+
+
+#ifndef NOSOUND
+  if (Opts_UsingSound())
+    Mix_PauseMusic();
+#endif
+
+
+  do
+  {
+    while (SDL_PollEvent(&event))
+    {
+      if (event.type == SDL_KEYDOWN)
+        pause_done = 1;
+      else if (event.type == SDL_QUIT)
+      {
+        SDL_quit_received = 1;
+         pause_quit = 1;
+      }
+    }
+
+    SDL_Delay(100);
+  }
+  while (!pause_done && !pause_quit);
+
+
+#ifndef NOSOUND
+  if (Opts_UsingSound())
+    Mix_ResumeMusic();
+#endif
+
+  return (pause_quit);
+}
+
+
+
+/* Draw a line: */
+
+void draw_line(int x1, int y1, int x2, int y2, int red, int grn, int blu)
+{
+  int dx, dy, tmp;
+  float m, b;
+  Uint32 pixel;
+  SDL_Rect dest;
+
+  pixel = SDL_MapRGB(screen->format, red, grn, blu);
+
+  dx = x2 - x1;
+  dy = y2 - y1;
+
+  putpixel(screen, x1, y1, pixel);
+
+  if (dx != 0)
+  {
+    m = ((float) dy) / ((float) dx);
+    b = y1 - m * x1;
+
+    if (x2 > x1)
+      dx = 1;
+    else
+      dx = -1;
+
+    while (x1 != x2)
+    {
+      x1 = x1 + dx;
+      y1 = m * x1 + b;
+
+      putpixel(screen, x1, y1, pixel);
+    }
+  }
+  else
+  {
+    if (y1 > y2)
+    {
+      tmp = y1;
+      y1 = y2;
+      y2 = tmp;
+    }
+
+    dest.x = x1;
+    dest.y = y1;
+    dest.w = 3;
+    dest.h = y2 - y1;
+
+    SDL_FillRect(screen, &dest, pixel);
+  }
+}
+
+
+/* Draw a single pixel into the surface: */
+
+void putpixel(SDL_Surface* surface, int x, int y, Uint32 pixel)
+{
+#ifdef PUTPIXEL_RAW
+  int bpp;
+  Uint8* p;
+
+  /* Determine bytes-per-pixel for the surface in question: */
+
+  bpp = surface->format->BytesPerPixel;
+
+
+  /* Set a pointer to the exact location in memory of the pixel
+     in question: */
+
+  p = (Uint8 *) (surface->pixels +       /* Start at beginning of RAM */
+                 (y * surface->pitch) +  /* Go down Y lines */
+                 (x * bpp));             /* Go in X pixels */
+
+
+  /* Assuming the X/Y values are within the bounds of this surface... */
+
+  if (x >= 0 && y >= 0 && x < surface->w && y < surface->h)
+    {
+      /* Set the (correctly-sized) piece of data in the surface's RAM
+         to the pixel value sent in: */
+
+      if (bpp == 1)
+        *p = pixel;
+      else if (bpp == 2)
+        *(Uint16 *)p = pixel;
+      else if (bpp == 3)
+        {
+          if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
+            {
+              p[0] = (pixel >> 16) & 0xff;
+              p[1] = (pixel >> 8) & 0xff;
+              p[2] = pixel & 0xff;
+            }
+          else
+            {
+              p[0] = pixel & 0xff;
+              p[1] = (pixel >> 8) & 0xff;
+              p[2] = (pixel >> 16) & 0xff;
+            }
+        }
+      else if (bpp == 4)
+        {
+          *(Uint32 *)p = pixel;
+        }
+    }
+#else
+  SDL_Rect dest;
+
+  dest.x = x;
+  dest.y = y;
+  dest.w = 3;
+  dest.h = 4;
+
+  SDL_FillRect(surface, &dest, pixel);
+#endif
+}
+
+
+/* Draw image at lower center of screen: */
+void draw_console_image(int i)
+{
+  SDL_Rect dest;
+
+  dest.x = (screen->w - images[i]->w) / 2;
+  dest.y = (screen->h - images[i]->h);
+  dest.w = images[i]->w;
+  dest.h = images[i]->h;
+
+  SDL_BlitSurface(images[i], NULL, screen, &dest);
+}
+
+
+void draw_question_counter(void)
+{
+  int questions_left;
+  int comet_img;
+  int nums_width;
+  int nums_x;
+  int comet_width;
+  int comet_x;
+
+  char str[64];
+  SDL_Rect dest;
+
+  /* Calculate placement based on image widths: */
+  nums_width = (images[IMG_NUMBERS]->w / 10) * 4; /* displaying 4 digits */
+  comet_width = images[IMG_MINI_COMET1]->w;
+  comet_x = (screen->w)/2 - (comet_width + nums_width)/2;
+  nums_x = comet_x + comet_width;
+
+  /* Draw mini comet symbol:            */
+  /* Decide which image to display: */
+  comet_img = IMG_MINI_COMET1 + (frame % 3);
+  /* Draw it! */
+  dest.x = comet_x;
+  dest.y = 0;
+  dest.w = comet_width;
+  dest.h = images[comet_img]->h;
+
+  SDL_BlitSurface(images[comet_img], NULL, screen, &dest);
+
+  /* draw number of remaining questions: */
+  questions_left = MC_TotalQuestionsLeft();
+  sprintf(str, "%.4d", questions_left);
+  draw_numbers(str, nums_x, 0);
+}
+
+/* FIXME very confusing having this function draw console */
+void draw_led_console(void)
+{
+  int i;
+  SDL_Rect src, dest;
+  int y;
+
+  /* draw new console image with "monitor" for LED numbers: */
+  draw_console_image(IMG_CONSOLE_LED);
+  /* set y to draw LED numbers into Tux's "monitor": */
+  y = (screen->h
+     - images[IMG_CONSOLE_LED]->h
+     + 4);  /* "monitor" has 4 pixel margin */
+
+  /* begin drawing so as to center display depending on whether minus */
+  /* sign needed (4 digit slots) or not (3 digit slots) DSB */
+  if (MC_GetOpt(ALLOW_NEGATIVES) )
+    dest.x = ((screen->w - ((images[IMG_LEDNUMS]->w) / 10) * 4) / 2);
+  else
+    dest.x = ((screen->w - ((images[IMG_LEDNUMS]->w) / 10) * 3) / 2);
+
+  for (i = -1; i < MC_MAX_DIGITS; i++) /* -1 is special case to allow minus sign */
+                              /* with minimal modification of existing code DSB */
+  {
+    if (-1 == i)
+    {
+      if (MC_GetOpt(ALLOW_NEGATIVES))
+      {
+        if (neg_answer_picked)
+          src.x =  (images[IMG_LED_NEG_SIGN]->w) / 2;
+        else
+          src.x = 0;
+
+        src.y = 0;
+        src.w = (images[IMG_LED_NEG_SIGN]->w) / 2;
+        src.h = images[IMG_LED_NEG_SIGN]->h;
+
+        dest.y = y;
+        dest.w = src.w;
+        dest.h = src.h;
+
+        SDL_BlitSurface(images[IMG_LED_NEG_SIGN], &src, screen, &dest);
+        /* move "cursor" */
+        dest.x += src.w;
+      }
+    }
+    else
+    {
+      src.x = digits[i] * ((images[IMG_LEDNUMS]->w) / 10);
+      src.y = 0;
+      src.w = (images[IMG_LEDNUMS]->w) / 10;
+      src.h = images[IMG_LEDNUMS]->h;
+
+      /* dest.x already set */
+      dest.y = y;
+      dest.w = src.w;
+      dest.h = src.h;
+
+      SDL_BlitSurface(images[IMG_LEDNUMS], &src, screen, &dest);
+      /* move "cursor" */
+      dest.x += src.w;
+    }
+  }
+}
+
+/* Translates mouse events into keyboard events when on-screen keypad used */
+/* or when exit button clicked.                                            */
+void game_mouse_event(SDL_Event event)
+{
+  int keypad_w, keypad_h, x, y, row, column;
+  SDLKey key = SDLK_UNKNOWN;
+
+  keypad_w = 0;
+  keypad_h = 0;
+
+  /* Check to see if user clicked exit button: */
+  /* The exit button is in the upper right corner of the screen: */
+  if ((event.button.x >= (screen->w - images[IMG_STOP]->w))
+    &&(event.button.y <= images[IMG_STOP]->h))
+  {
+    key = SDLK_ESCAPE;
+    game_key_event(key);
+    return;
+  }
+
+  /* get out unless we really are using keypad */
+  if ( level_start_wait
+    || Opts_DemoMode()
+    || !Opts_GetGlobalOpt(USE_KEYPAD))
+  {
+    return;
+  }
+
+
+  /* make sure keypad image is valid and has non-zero dimensions: */
+  /* FIXME maybe this checking should be done once at the start */
+  /* of game() rather than with every mouse click */
+  if (MC_GetOpt(ALLOW_NEGATIVES))
+  {
+    if (!images[IMG_KEYPAD])
+      return;
+    else
+    {
+      keypad_w = images[IMG_KEYPAD]->w;
+      keypad_h = images[IMG_KEYPAD]->h;
+    }
+  }
+  else
+  {
+    if (!images[IMG_KEYPAD_NO_NEG])
+      return;
+    else
+    {
+      keypad_w = images[IMG_KEYPAD]->w;
+      keypad_h = images[IMG_KEYPAD]->h;
+    }
+  }
+
+  if (!keypad_w || !keypad_h)
+  {
+    return;
+  }
+
+
+  /* only proceed if click falls within keypad: */
+  if (!((event.button.x >=
+        (screen->w / 2) - (keypad_w / 2) &&
+        event.button.x <=
+        (screen->w / 2) + (keypad_w / 2) &&
+        event.button.y >=
+        (screen->h / 2) - (keypad_h / 2) &&
+        event.button.y <=
+        (screen->h / 2) + (keypad_h / 2))))
+  /* click outside of keypad - do nothing */
+  {
+    return;
+  }
+
+  else /* click was within keypad */
+  {
+    x = (event.button.x - ((screen->w / 2) - (keypad_w / 2)));
+    y = (event.button.y - ((screen->h / 2) - (keypad_h / 2)));
+
+  /* Now determine what onscreen key was pressed */
+  /*                                             */
+  /* The on-screen keypad has a 4 x 4 layout:    */
+  /*                                             */
+  /*    *********************************        */
+  /*    *       *       *       *       *        */
+  /*    *   7   *   8   *   9   *   -   *        */
+  /*    *       *       *       *       *        */
+  /*    *********************************        */
+  /*    *       *       *       *       *        */
+  /*    *   4   *   5   *   6   *       *        */
+  /*    *       *       *       *       *        */
+  /*    *************************   +   *        */
+  /*    *       *       *       *       *        */
+  /*    *   1   *   2   *   3   *       *        */
+  /*    *       *       *       *       *        */
+  /*    *********************************        */
+  /*    *       *                       *        */
+  /*    *   0   *         Enter         *        */
+  /*    *       *                       *        */
+  /*    *********************************        */
+  /*                                             */
+  /*  The following code simply figures out the  */
+  /*  row and column based on x and y and looks  */
+  /*  up the SDlKey accordingly.                 */
+
+    column = x/((keypad_w)/4);
+    row    = y/((keypad_h)/4);
+
+    /* make sure row and column are sane */
+    if (column < 0
+     || column > 3
+     || row    < 0
+     || row    > 3)
+    {
+      printf("\nIllegal row or column value!\n");
+      return;
+    }
+
+    /* simple but tedious - I am sure this could be done more elegantly */
+
+    if (0 == row)
+    {
+      if (0 == column)
+        key = SDLK_7;
+      if (1 == column)
+        key = SDLK_8;
+      if (2 == column)
+        key = SDLK_9;
+      if (3 == column)
+        key = SDLK_MINUS;
+    }
+    if (1 == row)
+    {
+      if (0 == column)
+        key = SDLK_4;
+      if (1 == column)
+        key = SDLK_5;
+      if (2 == column)
+        key = SDLK_6;
+      if (3 == column)
+        key = SDLK_PLUS;
+    }
+    if (2 == row)
+    {
+      if (0 == column)
+        key = SDLK_1;
+      if (1 == column)
+        key = SDLK_2;
+      if (2 == column)
+        key = SDLK_3;
+      if (3 == column)
+        key = SDLK_PLUS;
+    }
+    if (3 == row)
+    {
+      if (0 == column)
+        key = SDLK_0;
+      if (1 == column)
+        key = SDLK_RETURN;
+      if (2 == column)
+        key = SDLK_RETURN;
+      if (3 == column)
+        key = SDLK_RETURN;
+    }
+
+    if (key == SDLK_UNKNOWN)
+    {
+      return;
+    }
+
+    /* now can proceed as if keyboard was used */
+    game_key_event(key);
+  }
+}
+
+/* called by either key presses or mouse clicks on */
+/* on-screen keypad */
+void game_key_event(SDLKey key)
+{
+  int i;
+  key_pressed = 1;   // Signal back in cases where waiting on any key
+
+  if (key == SDLK_ESCAPE)
+  {
+    /* Escape key - quit! */
+    escape_received = 1;
+  }
+
+  else if (key == SDLK_TAB
+        || key == SDLK_p)
+  {
+    /* [TAB] or [P]: Pause! (if settings allow) */
+    if (Opts_AllowPause())
+    {
+      paused = 1;
+    }
+  }
+
+  /* Adjust speed if settings allow: */
+  else if (key == SDLK_UP)
+  {
+    if (Opts_AllowPause())
+    {
+      speed *= 1.2;
+    }
+  }
+
+  else if (key == SDLK_DOWN)
+  {
+    if (Opts_AllowPause())
+    {
+      speed /= 1.2;
+    }
+  }
+
+
+  /* Toggle screen mode: */
+  else if (key == SDLK_F10)
+  {
+    Opts_SetGlobalOpt(FULLSCREEN, !Opts_GetGlobalOpt(FULLSCREEN) );
+    SwitchScreenMode();
+    game_recalc_positions();
+  }
+
+  /* Toggle music: */
+#ifndef NOSOUND
+  else if (key == SDLK_F11)
+  {
+    if (Opts_UsingSound())
+    {
+      if (Mix_PlayingMusic())
+      {
+        Mix_HaltMusic();
+      }
+      else
+      {
+        Mix_PlayMusic(musics[MUS_GAME + (rand() % 3)], 0);
+      }
+    }
+  }
+#endif
+
+
+  if (level_start_wait > 0 || Opts_DemoMode() || !help_controls.laser_enabled)
+  {
+    /* Eat other keys until level start wait has passed,
+    or if game is in demo mode: */
+    key = SDLK_UNKNOWN;
+  }
+
+
+  /* The rest of the keys control the numeric answer console: */
+
+  if (key >= SDLK_0 && key <= SDLK_9)
+  {
+    /* [0]-[9]: Add a new digit: */
+    for (i = 0; i < MC_MAX_DIGITS-1; ++i)
+      digits[i] = digits[i+1];
+    digits[MC_MAX_DIGITS-1] = key - SDLK_0;
+    
+//    digits[0] = digits[1];
+//    digits[1] = digits[2];
+//    digits[2] = key - SDLK_0;
+    tux_pressing = 1;
+  }
+  else if (key >= SDLK_KP0 && key <= SDLK_KP9)
+  {
+    /* Keypad [0]-[9]: Add a new digit: */
+    for (i = 0; i < MC_MAX_DIGITS-1; ++i)
+      digits[i] = digits[i+1];
+    digits[MC_MAX_DIGITS-1] = key - SDLK_KP0;
+    
+//    digits[0] = digits[1];
+//    digits[1] = digits[2];
+//    digits[2] = key - SDLK_KP0;
+    tux_pressing = 1;
+  }
+  /* support for negative answer input DSB */
+  else if ((key == SDLK_MINUS || key == SDLK_KP_MINUS)
+        && MC_GetOpt(ALLOW_NEGATIVES) )  /* do nothing unless neg answers allowed */
+  {
+    /* allow player to make answer negative: */
+    neg_answer_picked = 1;
+    tux_pressing = 1;
+  }
+  else if ((key == SDLK_PLUS || key == SDLK_KP_PLUS)
+         && MC_GetOpt(ALLOW_NEGATIVES) )  /* do nothing unless neg answers allowed */
+  {
+    /* allow player to make answer positive: */
+    neg_answer_picked = 0;
+    tux_pressing = 1;
+  }
+  else if (key == SDLK_BACKSPACE ||
+           key == SDLK_CLEAR ||
+           key == SDLK_DELETE)
+  {
+    /* [BKSP]: Clear digits! */
+    for (i = 0; i < MC_MAX_DIGITS; ++i)
+      digits[i] = 0;
+    tux_pressing = 1;
+  }
+  else if (key == SDLK_RETURN ||
+           key == SDLK_KP_ENTER ||
+           key == SDLK_SPACE)
+  {
+    /* [ENTER]: Accept digits! */
+    doing_answer = 1;
+  }
+}
+
+/* Increment score: */
+
+void add_score(int inc)
+{
+  score += inc;
+  tmdprintf("Score is now: %d\n", score);
+}
+
+
+
+void reset_comets(void)
+{
+  int i =0;
+  for (i = 0; i < MAX_COMETS; i++)
+  {
+    comets[i].alive = 0;
+    comets[i].expl = 0;
+    comets[i].city = 0;
+    comets[i].x = 0;
+    comets[i].y = 0;
+    comets[i].answer = 0;
+//    strncpy(comets[i].flashcard.formula_string, " ", max_formula_size);
+//    strncpy(comets[i].flashcard.answer_string, " ", max_answer_size);
+    MC_ResetFlashCard(&(comets[i].flashcard) );
+    comets[i].bonus = 0;
+  }
+}
+
+void print_status(void)
+{
+  int i;
+
+  printf("\nCities:");
+  printf("\nHits left: ");
+  for (i = 0; i < NUM_CITIES; i++)
+    printf("%02d ",cities[i].hits_left);
+  printf("\nStatus:    ");
+  for (i = 0; i < NUM_CITIES; i++)
+    printf("%02d ",cities[i].status);
+
+  printf("\nPenguins:");
+  printf("\nStatus:    ");
+  for (i = 0; i < NUM_CITIES; i++)
+    printf("%02d ",penguins[i].status);
+
+  printf("\nCloud:");
+  printf("\nStatus:    %d",cloud.status);
+  printf("\nCity:      %d",cloud.city);
+  printf("\n");
+}
+
+void free_on_exit(void)
+{
+  int i;
+  for (i = 0; i < MAX_MAX_COMETS; ++i)
+    MC_FreeFlashcard(&(comets[i].flashcard));
+  free(comets);
+  free(cities);
+  free(penguins);
+  free(steam);
+}
+
+/* Recalculate on-screen city & comet locations when screen dimensions change */
+void game_recalc_positions(void)
+{
+  int i, img;
+  int old_city_expl_height = city_expl_height;
+
+  tmdprintf("Recalculating positions\n");
+
+  if (Opts_GetGlobalOpt(USE_IGLOOS))
+    img = IMG_IGLOO_INTACT;
+  else
+    img = IMG_CITY_BLUE;
+
+  for (i = 0; i < NUM_CITIES; ++i)
+  {
+    /* Left vs. Right - makes room for Tux and the console */
+    if (i < NUM_CITIES / 2)
+    {
+      cities[i].x = (((screen->w / (NUM_CITIES + 1)) * i) +
+                     ((images[img] -> w) / 2));
+      tmdprintf("%d,", cities[i].x);
+    }
+    else
+    {
+      cities[i].x = screen->w -
+                   (screen->w / (NUM_CITIES + 1) *
+                   (i - NUM_CITIES / 2) +
+                    images[img]->w / 2);
+      tmdprintf("%d,", cities[i].x);
+    }
+
+    penguins[i].x = cities[i].x;
+  }
+
+  city_expl_height = screen->h - images[IMG_CITY_BLUE]->h;
+  //move comets to a location 'equivalent' to where they were
+  //i.e. with the same amount of time left before impact
+  for (i = 0; i < MAX_COMETS; ++i)
+  {
+    if (!comets[i].alive)
+      continue;
+
+    comets[i].x = cities[comets[i].city].x;
+    //if (Opts_GetGlobalOpt(FULLSCREEN) )
+      comets[i].y = comets[i].y * city_expl_height / old_city_expl_height;
+    //else
+    //  comets[i].y = comets[i].y * RES_Y / screen->h;
+  }
+
+
+}

Added: tuxmath/trunk/src/game.h
===================================================================
--- tuxmath/trunk/src/game.h	                        (rev 0)
+++ tuxmath/trunk/src/game.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,151 @@
+/*
+  game.h
+
+  For TuxMath
+  The main game loop!
+
+  by Bill Kendrick
+  bill at newbreedsoftware.com
+  http://www.newbreedsoftware.com/
+
+
+  Part of "Tux4Kids" Project
+  http://www.tux4kids.org/
+      
+  August 26, 2001 - February 18, 2004
+*/
+
+
+#ifndef GAME_H
+#define GAME_H
+
+#define MAX_COMETS 10
+#define NUM_CITIES 4   /* MUST BE AN EVEN NUMBER! */
+
+#define NUM_BKGDS 8
+
+#define MAX_CITY_COLORS 4
+
+typedef struct laser_type {
+  int alive;
+  int x1, y1;
+  int x2, y2;
+} laser_type;
+
+/* Note: both igloos and the original "cities" graphics are handled
+   with the "cities" structure.
+   hits_left holds the number of hits it can withstand before
+   being "dead". If using the original cities graphics,
+      2 = with shield,
+      1 = without shield,
+      0 = dead
+   If using the igloo graphics,
+      2 = intact,
+      1 = half-melted
+      0 = melted
+*/
+
+/* For both cities/igloos & penguins, the animation state is
+   controlled by "status", and "counter" is used for timing.  We also
+   have "img" and "layer" so that the image can be pre-planned to have
+   a specific rendering order (so that foreground/background issues
+   are handled properly). Layer 0 is rendered first, then layer 1, and
+   so on. */
+
+typedef struct city_type {
+  int hits_left;
+  int status, counter;
+  int threatened;   /* true if a comet is near */
+  int x;
+  int img,layer;
+} city_type;
+
+typedef struct penguin_type {
+  int status, counter;
+  int x;
+  int img,layer;
+} penguin_type;
+
+typedef struct steam_type {
+  int status, counter;
+  int img,layer;
+} steam_type;
+
+#define NUM_SNOWFLAKES 100
+
+typedef struct cloud_type {
+  int status;
+  int city;
+  int x,y;
+  int snowflake_x[NUM_SNOWFLAKES];
+  int snowflake_y[NUM_SNOWFLAKES];
+  int snowflake_size[NUM_SNOWFLAKES];
+} cloud_type;
+
+#define GAME_MESSAGE_LENGTH 100
+
+typedef struct {
+  int x,y;
+  int alpha;
+  char message[GAME_MESSAGE_LENGTH];
+} game_message;
+
+enum {
+  GAME_IN_PROGRESS,
+  GAME_OVER_WON,
+  GAME_OVER_LOST,
+  GAME_OVER_OTHER,
+  GAME_OVER_ESCAPE,
+  GAME_OVER_WINDOW_CLOSE,
+  GAME_OVER_ERROR
+};
+
+/* City animation status types */
+enum {
+  CITY_PRESENT,
+  CITY_EXPLODING,
+  CITY_EVAPORATING,
+  CITY_REBUILDING,
+  CITY_GONE
+};
+
+/* Penguin animation status types */
+enum {
+  PENGUIN_OFFSCREEN,
+  PENGUIN_HAPPY,
+  PENGUIN_FLAPPING,
+  PENGUIN_DUCKING,
+  PENGUIN_GRUMPY,
+  PENGUIN_WORRIED,
+  PENGUIN_WALKING_OFF,
+  PENGUIN_WALKING_ON,
+  PENGUIN_STANDING_UP,
+  PENGUIN_SITTING_DOWN,
+  PENGUIN_BOWING
+};
+
+/* Steam animation status types */
+enum {
+  STEAM_OFF,
+  STEAM_ON
+};
+
+/* Cloud & snowflake animation types */
+enum {
+  EXTRA_LIFE_OFF,
+  EXTRA_LIFE_ON
+};
+
+int game(void);
+void game_set_start_message(const char*, const char*, const char*, const char*);
+/* draw_nums() is used in options.c and factoroids.c/h so need extern linkage */
+
+void draw_nums(const char* str, int x, int y);
+
+/*used in factoroids.c/h*/
+int pause_game(void);
+//void putpixel(SDL_Surface* surface, int x, int y, Uint32 pixel);
+void draw_line(int x1, int y1, int x2, int y2, int r, int g, int b);
+void draw_numbers(const char* str, int x, int y);
+
+#endif

Added: tuxmath/trunk/src/generate_lesson.c
===================================================================
--- tuxmath/trunk/src/generate_lesson.c	                        (rev 0)
+++ tuxmath/trunk/src/generate_lesson.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,65 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "globals.h"
+#include "options.h"
+#include "mathcards.h"
+#include "fileops.h"
+
+/* Compile this with the following statement:
+
+gcc -lm -o generate_lesson -DDATA_PREFIX=\"/usr/local/share/tuxmath\" generate_lesson.c mathcards.c options.c fileops.c lesson.c
+
+Usage: generate_lesson configfile1 configfile2 ...
+
+*/
+
+/* Declarations needed for the auxillary functions */
+char **lesson_list_titles = NULL;
+char **lesson_list_filenames = NULL;
+int num_lessons = 0;
+
+int read_high_scores_fp(FILE* fp)
+{
+  /* This is a stub to let things compile */
+  return 1;
+}
+
+void initialize_scores(void)
+{
+  /* This is a stub to let things compile */
+}  
+
+int main(int argc,char *argv[])
+{
+  int i;
+
+  /* Initialize MathCards backend for math questions: */
+  if (!MC_Initialize())
+  {
+    printf("\nUnable to initialize MathCards\n");
+    fprintf(stderr, "\nUnable to initialize MathCards\n");
+    exit(1);
+  }
+
+  /* initialize game_options struct with defaults DSB */
+  if (!Opts_Initialize())
+  {
+    fprintf(stderr, "\nUnable to initialize game_options\n");
+    exit(1);
+  }
+
+  /* This next bit allows multiple config files to be read in sequence, since
+     this is something that happens in the ordinary course of events
+     in tuxmath itself. */
+  for (i = 1; i < argc; i++) {
+    printf("Reading %s\n",argv[i]);
+    read_named_config_file(argv[i]);
+  }
+  printf("All done reading!\n");
+
+  MC_StartGame();
+  MC_PrintQuestionList(stdout);
+  return 0;
+}

Added: tuxmath/trunk/src/gettext.h
===================================================================
--- tuxmath/trunk/src/gettext.h	                        (rev 0)
+++ tuxmath/trunk/src/gettext.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,265 @@
+/* Convenience header for conditional use of GNU <libintl.h>.
+   Copyright (C) 1995-1998, 2000-2002, 2004-2006 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify it
+   under the terms of the GNU Library General Public License as published
+   by the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+   USA.  */
+
+#ifndef _LIBGETTEXT_H
+#define _LIBGETTEXT_H 1
+
+/* NLS can be disabled through the configure --disable-nls option.  */
+#if ENABLE_NLS
+
+/* Get declarations of GNU message catalog functions.  */
+# include <libintl.h>
+
+/* You can set the DEFAULT_TEXT_DOMAIN macro to specify the domain used by
+   the gettext() and ngettext() macros.  This is an alternative to calling
+   textdomain(), and is useful for libraries.  */
+# ifdef DEFAULT_TEXT_DOMAIN
+#  undef gettext
+#  define gettext(Msgid) \
+     dgettext (DEFAULT_TEXT_DOMAIN, Msgid)
+#  undef ngettext
+#  define ngettext(Msgid1, Msgid2, N) \
+     dngettext (DEFAULT_TEXT_DOMAIN, Msgid1, Msgid2, N)
+# endif
+
+#else
+
+/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which
+   chokes if dcgettext is defined as a macro.  So include it now, to make
+   later inclusions of <locale.h> a NOP.  We don't include <libintl.h>
+   as well because people using "gettext.h" will not include <libintl.h>,
+   and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
+   is OK.  */
+#if defined(__sun)
+# include <locale.h>
+#endif
+
+/* Many header files from the libstdc++ coming with g++ 3.3 or newer include
+   <libintl.h>, which chokes if dcgettext is defined as a macro.  So include
+   it now, to make later inclusions of <libintl.h> a NOP.  */
+#if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3)
+# include <cstdlib>
+# if (__GLIBC__ >= 2) || _GLIBCXX_HAVE_LIBINTL_H
+#  include <libintl.h>
+# endif
+#endif
+
+/* Disabled NLS.
+   The casts to 'const char *' serve the purpose of producing warnings
+   for invalid uses of the value returned from these functions.
+   On pre-ANSI systems without 'const', the config.h file is supposed to
+   contain "#define const".  */
+# define gettext(Msgid) ((const char *) (Msgid))
+# define dgettext(Domainname, Msgid) ((const char *) (Msgid))
+# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid))
+# define ngettext(Msgid1, Msgid2, N) \
+    ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+# define dngettext(Domainname, Msgid1, Msgid2, N) \
+    ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
+    ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+# define textdomain(Domainname) ((const char *) (Domainname))
+# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname))
+# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset))
+
+#endif
+
+/* A pseudo function call that serves as a marker for the automated
+   extraction of messages, but does not call gettext().  The run-time
+   translation is done at a different place in the code.
+   The argument, String, should be a literal string.  Concatenated strings
+   and other string expressions won't work.
+   The macro's expansion is not parenthesized, so that it is suitable as
+   initializer for static 'char[]' or 'const char[]' variables.  */
+#define gettext_noop(String) String
+
+/* The separator between msgctxt and msgid in a .mo file.  */
+#define GETTEXT_CONTEXT_GLUE "\004"
+
+/* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a
+   MSGID.  MSGCTXT and MSGID must be string literals.  MSGCTXT should be
+   short and rarely need to change.
+   The letter 'p' stands for 'particular' or 'special'.  */
+#ifdef DEFAULT_TEXT_DOMAIN
+# define pgettext(Msgctxt, Msgid) \
+   pgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
+#else
+# define pgettext(Msgctxt, Msgid) \
+   pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
+#endif
+#define dpgettext(Domainname, Msgctxt, Msgid) \
+  pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
+#define dcpgettext(Domainname, Msgctxt, Msgid, Category) \
+  pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, Category)
+#ifdef DEFAULT_TEXT_DOMAIN
+# define npgettext(Msgctxt, Msgid, MsgidPlural, N) \
+   npgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
+#else
+# define npgettext(Msgctxt, Msgid, MsgidPlural, N) \
+   npgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
+#endif
+#define dnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
+  npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
+#define dcnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N, Category) \
+  npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, Category)
+
+#ifdef __GNUC__
+__inline
+#else
+#ifdef __cplusplus
+inline
+#endif
+#endif
+static const char *
+pgettext_aux (const char *domain,
+              const char *msg_ctxt_id, const char *msgid,
+              int category)
+{
+  const char *translation = dcgettext (domain, msg_ctxt_id, category);
+  if (translation == msg_ctxt_id)
+    return msgid;
+  else
+    return translation;
+}
+
+#ifdef __GNUC__
+__inline
+#else
+#ifdef __cplusplus
+inline
+#endif
+#endif
+static const char *
+npgettext_aux (const char *domain,
+               const char *msg_ctxt_id, const char *msgid,
+               const char *msgid_plural, unsigned long int n,
+               int category)
+{
+  const char *translation =
+    dcngettext (domain, msg_ctxt_id, msgid_plural, n, category);
+  if (translation == msg_ctxt_id || translation == msgid_plural)
+    return (n == 1 ? msgid : msgid_plural);
+  else
+    return translation;
+}
+
+/* The same thing extended for non-constant arguments.  Here MSGCTXT and MSGID
+   can be arbitrary expressions.  But for string literals these macros are
+   less efficient than those above.  */
+
+#include <string.h>
+
+#define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS \
+  (__GNUC__ >= 3 || __GNUG__ >= 2 /* || __STDC_VERSION__ >= 199901L */ )
+
+#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
+#include <stdlib.h>
+#endif
+
+#define pgettext_expr(Msgctxt, Msgid) \
+  dcpgettext_expr (NULL, Msgctxt, Msgid, LC_MESSAGES)
+#define dpgettext_expr(Domainname, Msgctxt, Msgid) \
+  dcpgettext_expr (Domainname, Msgctxt, Msgid, LC_MESSAGES)
+
+#ifdef __GNUC__
+__inline
+#else
+#ifdef __cplusplus
+inline
+#endif
+#endif
+static const char *
+dcpgettext_expr (const char *domain,
+                 const char *msgctxt, const char *msgid,
+                 int category)
+{
+  size_t msgctxt_len = strlen (msgctxt) + 1;
+  size_t msgid_len = strlen (msgid) + 1;
+  const char *translation;
+#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
+  char msg_ctxt_id[msgctxt_len + msgid_len];
+#else
+  char buf[1024];
+  char *msg_ctxt_id =
+    (msgctxt_len + msgid_len <= sizeof (buf)
+     ? buf
+     : (char *) malloc (msgctxt_len + msgid_len));
+  if (msg_ctxt_id != NULL)
+#endif
+    {
+      memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1);
+      msg_ctxt_id[msgctxt_len - 1] = '\004';
+      memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len);
+      translation = dcgettext (domain, msg_ctxt_id, category);
+#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
+      if (msg_ctxt_id != buf)
+        free (msg_ctxt_id);
+#endif
+      if (translation != msg_ctxt_id)
+        return translation;
+    }
+  return msgid;
+}
+
+#define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N) \
+  dcnpgettext_expr (NULL, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES)
+#define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
+  dcnpgettext_expr (Domainname, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES)
+
+#ifdef __GNUC__
+__inline
+#else
+#ifdef __cplusplus
+inline
+#endif
+#endif
+static const char *
+dcnpgettext_expr (const char *domain,
+                  const char *msgctxt, const char *msgid,
+                  const char *msgid_plural, unsigned long int n,
+                  int category)
+{
+  size_t msgctxt_len = strlen (msgctxt) + 1;
+  size_t msgid_len = strlen (msgid) + 1;
+  const char *translation;
+#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
+  char msg_ctxt_id[msgctxt_len + msgid_len];
+#else
+  char buf[1024];
+  char *msg_ctxt_id =
+    (msgctxt_len + msgid_len <= sizeof (buf)
+     ? buf
+     : (char *) malloc (msgctxt_len + msgid_len));
+  if (msg_ctxt_id != NULL)
+#endif
+    {
+      memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1);
+      msg_ctxt_id[msgctxt_len - 1] = '\004';
+      memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len);
+      translation = dcngettext (domain, msg_ctxt_id, msgid_plural, n, category);
+#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
+      if (msg_ctxt_id != buf)
+        free (msg_ctxt_id);
+#endif
+      if (!(translation == msg_ctxt_id || translation == msgid_plural))
+        return translation;
+    }
+  return (n == 1 ? msgid : msgid_plural);
+}
+
+#endif /* _LIBGETTEXT_H */

Added: tuxmath/trunk/src/globals.h
===================================================================
--- tuxmath/trunk/src/globals.h	                        (rev 0)
+++ tuxmath/trunk/src/globals.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,133 @@
+/*
+  globals.h
+
+  For TuxMath
+
+  Contains global data for configuration of math questions and for
+  general game options, as well as constants and defaults.  Nothing
+  depending on SDL should be in here; put any SDL-related items into
+  tuxmath.h.
+
+  Author: David Bruce <dbruce at tampabay.rr.com>, (C) 2006
+
+
+  Part of "Tux4Kids" Project
+  http://www.tux4kids.org/
+      
+  Added March 2, 2006
+
+  Copyright: See COPYING file that comes with this distribution
+  (briefly - GNU GPL v2 or later)
+*/
+
+
+
+#ifndef GLOBALS_H
+#define GLOBALS_H
+
+#include "config.h"
+/* for conditional compilation of debugging output */
+//#define TUXMATH_DEBUG
+/* for Tim's feedback speed control code           */
+//#define FEEDBACK_DEBUG
+#define LINEBREAK
+/* nice inline debugging macro */
+#ifdef TUXMATH_DEBUG
+#define tmdprintf(...) printf(__VA_ARGS__)
+#else
+#define tmdprintf(...) 0
+#endif
+
+/* Maximum length of file path: */
+#define PATH_MAX 4096
+
+/* Error code if game_options not valid: */
+#define GAME_OPTS_INVALID 9999
+
+/* Default values for game_options */
+/* They can be changed in the struct to other values at run-time */
+#define DEFAULT_PER_USER_CONFIG 1
+#define DEFAULT_USE_SOUND 1
+#define DEFAULT_MENU_SOUND 1
+#define DEFAULT_MENU_MUSIC 1
+#define DEFAULT_FULLSCREEN 1
+#define DEFAULT_USE_BKGD 1
+#define DEFAULT_HELP_MODE 0
+#define DEFAULT_DEMO_MODE 0
+#define DEFAULT_OPER_OVERRIDE 0
+#define DEFAULT_USE_KEYPAD 0
+#define DEFAULT_ALLOW_PAUSE 1
+#define DEFAULT_BONUS_SPEED_RATIO 1.5
+#define DEFAULT_BONUS_COMET_INTERVAL 10
+#define DEFAULT_SPEED 1
+#define DEFAULT_ALLOW_SPEEDUP 1
+#define DEFAULT_SPEEDUP_FACTOR 1.2
+#define DEFAULT_MAX_SPEED 10
+#define DEFAULT_SLOW_AFTER_WRONG 0
+#define DEFAULT_STARTING_COMETS 2
+#define DEFAULT_EXTRA_COMETS_PER_WAVE 2
+#define DEFAULT_MAX_COMETS 10
+#define DEFAULT_SAVE_SUMMARY 1        
+#define DEFAULT_SOUND_HW_AVAILABLE 1
+#define DEFAULT_USE_IGLOOS 1
+#define DEFAULT_USE_FEEDBACK 0
+#define DEFAULT_DANGER_LEVEL 0.35
+#define DEFAULT_DANGER_LEVEL_SPEEDUP 1.1
+#define DEFAULT_DANGER_LEVEL_MAX 0.9
+#define DEFAULT_CITY_EXPL_HANDICAP 0
+#define DEFAULT_LAST_SCORE 0
+
+/* These values are hard-coded and used 'as is' by the program */
+/* (i.e. these behaviors require recompilation to change)   */ 
+#define DEFAULT_NUM_CITIES 4   /* MUST BE AN EVEN NUMBER! */
+#define DEFAULT_MAX_CITY_COLORS 4
+
+#define MINIMUM_SPEED 0.8
+#define MAX_MAX_SPEED 20.0
+#define MIN_SPEEDUP_FACTOR 1.0
+#define MAX_SPEEDUP_FACTOR 2.0
+#define MAX_BONUS_SPEED_RATIO 3.0
+#define MIN_COMETS 1
+#define MAX_MAX_COMETS 100
+
+#define DEFAULT_FONT_NAME "AndikaDesRevG.ttf"
+#define DEFAULT_MENU_FONT_SIZE 18
+#define DEFAULT_HELP_FONT_SIZE 32
+
+
+#define HIGH_SCORES_SAVED 10
+#define HIGH_SCORE_NAME_LENGTH 32
+
+#define REG_RGBA 16,16,96,96
+#define SEL_RGBA 16,16,128,128
+
+#define RES_X        640
+#define RES_Y        480
+#define PIXEL_BITS 32        
+
+enum { 
+  CADET_HIGH_SCORE,
+  SCOUT_HIGH_SCORE,
+  RANGER_HIGH_SCORE,
+  ACE_HIGH_SCORE,
+  COMMANDO_HIGH_SCORE,
+  NUM_MATH_COMMAND_LEVELS
+};
+
+enum {
+  FACTORS_HIGH_SCORE = NUM_MATH_COMMAND_LEVELS,
+  FRACTIONS_HIGH_SCORE,
+  NUM_HIGH_SCORE_LEVELS
+};
+
+
+
+#define NAME_BUF_SIZE 200
+
+/* data for 'Training Academy' lessons: */
+extern char **lesson_list_titles;
+extern char **lesson_list_filenames;
+extern int* lesson_list_goldstars;
+extern int num_lessons;
+
+#endif

Added: tuxmath/trunk/src/highscore.c
===================================================================
--- tuxmath/trunk/src/highscore.c	                        (rev 0)
+++ tuxmath/trunk/src/highscore.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,851 @@
+/*
+*  C Implementation: highscore.c
+*
+* Description: Implementation of high score tables for tuxmath.
+*
+*
+* Author: David Bruce <dbruce at tampabay.rr.com>, (C) 2007
+*
+* Copyright: See COPYING file that comes with this distribution
+* (Briefly, GNU GPL version 2 or greater).
+*/
+#include <string.h>
+
+#include "tuxmath.h"
+#include "highscore.h"
+#include "titlescreen.h"
+#include "fileops.h"
+#include "setup.h"
+#include "options.h"
+#include "SDL_extras.h"
+
+
+typedef struct high_score_entry {
+  int score;
+  char name[HIGH_SCORE_NAME_LENGTH];
+} high_score_entry;
+
+
+high_score_entry high_scores[NUM_HIGH_SCORE_LEVELS][HIGH_SCORES_SAVED];
+
+/* Local function prototypes: */
+
+
+
+/* Display high scores: */
+void DisplayHighScores(int level)
+{
+  int i = 0;
+  int finished = 0;
+  int tux_frame = 0;
+  Uint32 frame = 0;
+  Uint32 start = 0;
+
+  int diff_level = level;
+  int old_diff_level = -1; //So table gets refreshed first time through
+  /* Surfaces, char buffers, and rects for table: */
+  SDL_Surface* score_surfs[HIGH_SCORES_SAVED] = {NULL};
+
+  /* 10 spaces should be enough room for place and score on each line: */
+  char score_strings[HIGH_SCORES_SAVED][HIGH_SCORE_NAME_LENGTH + 10] = {{'\0'}};
+
+  SDL_Rect score_rects[HIGH_SCORES_SAVED];
+  SDL_Rect leftRect, rightRect, stopRect, TuxRect, table_bg;
+
+
+  const int max_width = 300;
+  int score_table_y = 100;
+
+  sprite* Tux = LoadSprite("tux/bigtux", IMG_ALPHA);
+
+  TTF_Font* title_font = LoadFont(DEFAULT_FONT_NAME, 32);
+  TTF_Font* player_font = LoadFont(DEFAULT_FONT_NAME, 14);
+  if (!player_font || !title_font)
+    return;  // Get out if we can't load fonts - should not happen!
+
+  /* --- Set up the rects for drawing various things: ----------- */
+
+  /* Put arrow buttons in right lower corner, inset by 20 pixels */
+  /* with a 10 pixel space between:                              */
+  if (images[IMG_RIGHT])
+  {
+    rightRect.w = images[IMG_RIGHT]->w;
+    rightRect.h = images[IMG_RIGHT]->h;
+    rightRect.x = screen->w - images[IMG_RIGHT]->w - 20;
+    rightRect.y = screen->h - images[IMG_RIGHT]->h - 20;
+  }
+
+  if (images[IMG_LEFT])
+  {
+    leftRect.w = images[IMG_LEFT]->w;
+    leftRect.h = images[IMG_LEFT]->h;
+    leftRect.x = rightRect.x - 10 - images[IMG_LEFT]->w;
+    leftRect.y = screen->h - images[IMG_LEFT]->h - 20;
+  }
+
+  /* Red "Stop" circle in upper right corner to go back to main menu: */
+  if (images[IMG_STOP])
+  {
+    stopRect.w = images[IMG_STOP]->w;
+    stopRect.h = images[IMG_STOP]->h;
+    stopRect.x = screen->w - images[IMG_STOP]->w;
+    stopRect.y = 0;
+  }
+
+  if (Tux && Tux->frame[0]) /* make sure sprite has at least one frame */
+   {
+    TuxRect.w = Tux->frame[0]->w;
+    TuxRect.h = Tux->frame[0]->h;
+    TuxRect.x = 0;
+    TuxRect.y = screen->h - Tux->frame[0]->h;
+   }
+
+
+  while (!finished)
+  {
+    start = SDL_GetTicks();
+
+    /* Check for user events: */
+    while (SDL_PollEvent(&event)) 
+    {
+      switch (event.type)
+      {
+        case SDL_QUIT:
+        {
+          cleanup();
+        }
+
+        case SDL_MOUSEBUTTONDOWN:
+        /* "Stop" button - go to main menu: */
+        { 
+          if (inRect(stopRect, event.button.x, event.button.y ))
+          {
+            finished = 1;
+            playsound(SND_TOCK);
+          }
+
+          /* "Left" button - go to previous page: */
+          if (inRect(leftRect, event.button.x, event.button.y))
+          {
+            if (diff_level > CADET_HIGH_SCORE)
+            {
+              diff_level--;
+              if (Opts_GetGlobalOpt(MENU_SOUND))
+              {
+                playsound(SND_TOCK);
+              }
+            }
+          }
+
+          /* "Right" button - go to next page: */
+          if (inRect( rightRect, event.button.x, event.button.y ))
+          {
+            if (diff_level < (NUM_HIGH_SCORE_LEVELS-1))
+            {
+              diff_level++;
+              if (Opts_GetGlobalOpt(MENU_SOUND))
+              {
+                playsound(SND_TOCK);
+              }
+            }
+          }
+          break;
+        }
+
+
+        case SDL_KEYDOWN:
+        {
+          finished = 1;
+          playsound(SND_TOCK);
+        }
+      }
+    }
+
+
+    /* If needed, redraw: */
+    if (diff_level != old_diff_level)
+    {
+      /* Draw background: */
+      if (current_bkg())
+        SDL_BlitSurface( current_bkg(), NULL, screen, NULL );
+      /* FIXME maybe add image of trophy or similar pic */
+      /* Draw Tux: */
+      if (Tux && Tux->frame[0]) /* make sure sprite has at least one frame */
+        SDL_BlitSurface(Tux->frame[0], NULL, screen, &TuxRect);
+      /* Draw controls: */
+      if (images[IMG_STOP])
+        SDL_BlitSurface(images[IMG_STOP], NULL, screen, &stopRect);
+      /* Draw regular or grayed-out left arrow: */
+      if (diff_level == CADET_HIGH_SCORE)
+      {
+        if (images[IMG_LEFT_GRAY])
+          SDL_BlitSurface(images[IMG_LEFT_GRAY], NULL, screen, &leftRect);
+      }
+      else
+      {
+        if (images[IMG_LEFT])
+          SDL_BlitSurface(images[IMG_LEFT], NULL, screen, &leftRect);
+      }
+      /* Draw regular or grayed-out right arrow: */
+      if (diff_level == NUM_HIGH_SCORE_LEVELS - 1)
+      {
+        if (images[IMG_RIGHT_GRAY])
+          SDL_BlitSurface(images[IMG_RIGHT_GRAY], NULL, screen, &rightRect);
+      }
+      else
+      {
+        if (images[IMG_RIGHT])
+          SDL_BlitSurface(images[IMG_RIGHT], NULL, screen, &rightRect);
+      }
+
+      /* Draw background shading for table: */
+      table_bg.x = (screen->w)/2 - (max_width + 20)/2 + 50; //don't draw over Tux
+      table_bg.y = 5;
+      table_bg.w = max_width + 20;
+      table_bg.h = screen->h - 10 - images[IMG_RIGHT]->h;
+      DrawButton(&table_bg, 25, SEL_RGBA);
+
+      /* Draw difficulty level heading: */
+      {
+        SDL_Surface* srfc = NULL;
+        SDL_Rect text_rect, button_rect;
+
+        srfc = BlackOutline(_("Hall Of Fame"), title_font, &yellow);
+        if (srfc)
+        {
+          button_rect.x = text_rect.x = (screen->w)/2 - (srfc->w)/2 + 50;
+          button_rect.y = text_rect.y = 10;
+          button_rect.w = text_rect.w = srfc->w;
+          button_rect.h = text_rect.h = srfc->h;
+          /* add margin to button and draw: */
+          button_rect.x -= 10;
+          button_rect.w += 20;
+          DrawButton(&button_rect, 15, 0, 0, 32, 192);
+          /* Now blit text and free surface: */
+          SDL_BlitSurface(srfc, NULL, screen, &text_rect);
+          SDL_FreeSurface(srfc);
+          srfc = NULL;
+        }
+
+        if (title_font)
+        {
+          switch (diff_level)
+          {
+            case CADET_HIGH_SCORE:
+              srfc = BlackOutline(_("Space Cadet"), title_font, &white);
+              break;
+            case SCOUT_HIGH_SCORE:
+              srfc = BlackOutline(_("Scout"), title_font, &white);
+              break;
+            case RANGER_HIGH_SCORE:
+              srfc = BlackOutline(_("Ranger"), title_font, &white);
+              break;
+            case ACE_HIGH_SCORE:
+              srfc = BlackOutline(_("Ace"), title_font, &white);
+              break;
+            case COMMANDO_HIGH_SCORE:
+              srfc = BlackOutline(_("Commando"), title_font, &white);
+              break;
+            case FACTORS_HIGH_SCORE:
+              srfc = BlackOutline(_("Factors"), title_font, &white);
+              break;
+            case FRACTIONS_HIGH_SCORE:
+              srfc = BlackOutline(_("Fractions"), title_font, &white);
+              break;
+            default:
+              srfc = BlackOutline(_("Space Cadet"), title_font, &white);
+          }
+        }
+
+        if (srfc)
+        {
+          text_rect.x = (screen->w)/2 - (srfc->w)/2 + 50; 
+          text_rect.y += text_rect.h; /* go to bottom of first line */
+          text_rect.w = srfc->w;
+          text_rect.h = srfc->h;
+          SDL_BlitSurface(srfc, NULL, screen, &text_rect);
+          SDL_FreeSurface(srfc);
+          srfc = NULL;
+          /* note where score table will start: */
+          score_table_y = text_rect.y + text_rect.h;
+        }
+      }
+
+
+      /* Generate and draw desired table: */
+
+      for (i = 0; i < HIGH_SCORES_SAVED; i++)
+      {
+        /* Get data for entries: */
+        sprintf(score_strings[i],
+                "%d.    %d     %s",
+                i + 1,                  /* Add one to get common-language place number */
+                HS_Score(diff_level, i),
+                HS_Name(diff_level, i));
+
+        /* Clear out old surfaces and update: */
+        if (score_surfs[i])               /* this should not happen! */
+          SDL_FreeSurface(score_surfs[i]);
+        
+        if (HS_Score(diff_level, i) == Opts_LastScore() && frame % 5 < 2)
+          score_surfs[i] = BlackOutline(N_(score_strings[i]), player_font, &yellow);
+        else
+          score_surfs[i] = BlackOutline(N_(score_strings[i]), player_font, &white);
+
+        /* Get out if BlackOutline() fails: */
+        if (!score_surfs[i])
+          continue;
+         
+        /* Set up entries in vertical column: */
+        if (0 == i)
+          score_rects[i].y = score_table_y;
+        else
+          score_rects[i].y = score_rects[i - 1].y + score_rects[i - 1].h;
+
+        score_rects[i].x = (screen->w)/2 - max_width/2 + 50;
+        score_rects[i].h = score_surfs[i]->h;
+        score_rects[i].w = max_width;
+
+        SDL_BlitSurface(score_surfs[i], NULL, screen, &score_rects[i]);
+        SDL_FreeSurface(score_surfs[i]);
+        score_surfs[i] = NULL;
+      }
+      /* Update screen: */
+      SDL_UpdateRect(screen, 0, 0, 0, 0);
+
+      old_diff_level = diff_level;
+    }
+
+
+    /* --- make tux blink --- */
+    switch (frame % TUX6)
+    {
+      case 0:    tux_frame = 1; break;
+      case TUX1: tux_frame = 2; break;
+      case TUX2: tux_frame = 3; break;
+      case TUX3: tux_frame = 4; break;                        
+      case TUX4: tux_frame = 3; break;
+      case TUX5: tux_frame = 2; break;
+      default: tux_frame = 0;
+    }
+
+    if (Tux && tux_frame)
+    {
+      SDL_BlitSurface(Tux->frame[tux_frame - 1], NULL, screen, &TuxRect);
+      SDL_UpdateRect(screen, TuxRect.x, TuxRect.y, TuxRect.w, TuxRect.h);
+    }
+
+
+    /* Wait so we keep frame rate constant: */
+    while ((SDL_GetTicks() - start) < 33)
+    {
+      SDL_Delay(20);
+    }
+    frame++;
+  }  // End of while (!finished) loop
+  TTF_CloseFont(title_font);
+  TTF_CloseFont(player_font);
+  title_font = player_font = NULL;
+  FreeSprite(Tux);
+}
+
+
+/* Display screen to allow player to enter name for high score table:     */
+/* The pl_name argument *must* point to a validly allocated string array  */
+/* at least three times HIGH_SCORE_NAME_LENGTH because UTF-8 is a         */
+/* multibyte encoding.                                                    */
+void HighScoreNameEntry(char* pl_name)
+{
+  NameEntry(pl_name, _("You Are In The Hall of Fame!"), _("Enter Your Name:"));
+}
+
+void NameEntry(char* pl_name, const char* heading, const char* sub)
+{
+  char UTF8_buf[HIGH_SCORE_NAME_LENGTH * 3] = {'\0'};
+
+  SDL_Rect loc;
+  SDL_Rect redraw_rect;
+  SDL_Rect TuxRect,
+           stopRect;
+
+  int redraw = 0;
+  int first_draw = 1;
+  int finished = 0;
+  int tux_frame = 0;
+  Uint32 frame = 0;
+  Uint32 start = 0;
+  wchar_t wchar_buf[HIGH_SCORE_NAME_LENGTH + 1] = {'\0'};
+  TTF_Font* name_font = NULL;
+  const int NAME_FONT_SIZE = 32;
+  const int BG_Y = 100;
+  const int BG_WIDTH = 400;
+  const int BG_HEIGHT = 200;
+
+  sprite* Tux = LoadSprite("tux/bigtux", IMG_ALPHA);
+
+  if (!pl_name)
+    return;
+    
+  name_font = LoadFont(DEFAULT_FONT_NAME, NAME_FONT_SIZE);
+  if (!name_font)
+    return;
+
+  /* We need to get Unicode vals from SDL keysyms */
+  SDL_EnableUNICODE(SDL_ENABLE);
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "\nEnter HighScoreNameEntry()\n" );
+#endif
+
+
+  /* Draw background: */
+  if (current_bkg())
+    SDL_BlitSurface(current_bkg(), NULL, screen, NULL);
+
+  /* Red "Stop" circle in upper right corner to go back to main menu: */
+  if (images[IMG_STOP])
+  {
+    stopRect.w = images[IMG_STOP]->w;
+    stopRect.h = images[IMG_STOP]->h;
+    stopRect.x = screen->w - images[IMG_STOP]->w;
+    stopRect.y = 0;
+    SDL_BlitSurface(images[IMG_STOP], NULL, screen, &stopRect);
+  }
+
+  if (Tux && Tux->frame[0]) /* make sure sprite has at least one frame */
+  {
+    TuxRect.w = Tux->frame[0]->w;
+    TuxRect.h = Tux->frame[0]->h;
+    TuxRect.x = 0;
+    TuxRect.y = screen->h - Tux->frame[0]->h;
+  }
+
+  /* Draw translucent background for text: */
+  {
+    SDL_Rect bg_rect;
+    bg_rect.x = (screen->w)/2 - BG_WIDTH/2;
+    bg_rect.y = BG_Y;
+    bg_rect.w = BG_WIDTH;
+    bg_rect.h = BG_HEIGHT;
+    DrawButton(&bg_rect, 15, REG_RGBA);
+
+    bg_rect.x += 10;
+    bg_rect.y += 10;
+    bg_rect.w -= 20;
+    bg_rect.h = 60;
+    DrawButton(&bg_rect, 10, SEL_RGBA);
+  }
+
+  /* Draw heading: */
+  {
+    SDL_Surface* s = BlackOutline(_(heading),
+                                  default_font, &white);
+    if (s)
+    {
+      loc.x = (screen->w/2) - (s->w/2);
+      loc.y = 110;
+      SDL_BlitSurface(s, NULL, screen, &loc);
+      SDL_FreeSurface(s);
+    }
+
+    s = BlackOutline(_(sub),
+                     default_font, &white);
+    if (s)
+    {
+      loc.x = (screen->w/2) - (s->w/2);
+      loc.y = 140;
+      SDL_BlitSurface(s, NULL, screen, &loc);
+      SDL_FreeSurface(s);
+    }
+  }
+
+  /* and update: */
+  SDL_UpdateRect(screen, 0, 0, 0, 0);
+
+
+
+  while (!finished)
+  {
+    start = SDL_GetTicks();
+
+    while (SDL_PollEvent(&event)) 
+    {
+      switch (event.type)
+      {
+        case SDL_QUIT:
+        {
+          cleanup();
+        }
+
+        case SDL_MOUSEBUTTONDOWN:
+        /* "Stop" button - go to main menu: */
+        { 
+          if (inRect(stopRect, event.button.x, event.button.y ))
+          {
+            finished = 1;
+            playsound(SND_TOCK);
+            break;
+          }
+        }
+        case SDL_KEYDOWN:
+        {
+#ifdef TUXMATH_DEBUG
+          fprintf(stderr, "Before keypress, string is %s\tlength = %d\n",
+                  wchar_buf, (int)wcslen(wchar_buf));
+#endif
+          switch (event.key.keysym.sym)
+          {
+            case SDLK_ESCAPE:
+            case SDLK_RETURN:
+            case SDLK_KP_ENTER:
+            {
+              finished = 1;
+              playsound(SND_TOCK);
+              break;
+            }
+            case SDLK_BACKSPACE:
+            {
+              if (wcslen(wchar_buf) > 0)
+                wchar_buf[(int)wcslen(wchar_buf) - 1] = '\0';
+              redraw = 1;
+              break;
+            }
+
+            /* For any other keys, if the key has a Unicode value, */
+            /* we add it to our string:                            */
+            default:
+            {
+              if ((event.key.keysym.unicode > 0)
+              && (wcslen(wchar_buf) < HIGH_SCORE_NAME_LENGTH)) 
+              {
+                wchar_buf[(int)wcslen(wchar_buf)] = event.key.keysym.unicode;
+                redraw = 1;
+              } 
+            }
+          }  /* end  'switch (event.key.keysym.sym)'  */
+
+#ifdef TUXMATH_DEBUG
+          fprintf(stderr, "After keypress, string is %s\tlength = %d\n",
+                    wchar_buf, (int)wcslen(wchar_buf));
+#endif
+            /* Now draw name, if needed: */
+          if (redraw)
+          {
+            SDL_Surface* s = NULL;
+            redraw = 0;
+
+            /* Convert text to UTF-8 so BlackOutline() can handle it: */
+   //         wcstombs((char*) UTF8_buf, wchar_buf, HIGH_SCORE_NAME_LENGTH * 3);
+            ConvertToUTF8(wchar_buf, UTF8_buf, HIGH_SCORE_NAME_LENGTH * 3);
+            /* Redraw background and shading in area where we drew text last time: */ 
+            if (!first_draw)
+            {
+              SDL_BlitSurface(current_bkg(), &redraw_rect, screen, &redraw_rect);
+              DrawButton(&redraw_rect, 0, REG_RGBA);
+              SDL_UpdateRect(screen,
+                             redraw_rect.x,
+                             redraw_rect.y,
+                             redraw_rect.w,
+                             redraw_rect.h);
+            }
+
+            s = BlackOutline(UTF8_buf, name_font, &yellow);
+            if (s)
+            {
+              /* set up loc and blit: */
+              loc.x = (screen->w/2) - (s->w/2);
+              loc.y = 200;
+              SDL_BlitSurface(s, NULL, screen, &loc);
+
+              /* Remember where we drew so we can update background next time through:  */
+              /* (for some reason we need to update a wider area to get clean image)    */
+              redraw_rect.x = loc.x - 20;
+              redraw_rect.y = loc.y - 10;
+              redraw_rect.h = s->h + 20;
+              redraw_rect.w = s->w + 40;
+              first_draw = 0;
+
+              SDL_UpdateRect(screen,
+                             redraw_rect.x,
+                             redraw_rect.y,
+                             redraw_rect.w,
+                             redraw_rect.h);
+              SDL_FreeSurface(s);
+              s = NULL;
+            }
+          }
+        }
+      }
+    }
+ 
+    /* --- make tux blink --- */
+    switch (frame % TUX6)
+    {
+      case 0:    tux_frame = 1; break;
+      case TUX1: tux_frame = 2; break;
+      case TUX2: tux_frame = 3; break;
+      case TUX3: tux_frame = 4; break;                        
+      case TUX4: tux_frame = 3; break;
+      case TUX5: tux_frame = 2; break;
+      default: tux_frame = 0;
+    }
+
+    if (Tux && tux_frame)
+    {
+      SDL_BlitSurface(Tux->frame[tux_frame - 1], NULL, screen, &TuxRect);
+      SDL_UpdateRect(screen, TuxRect.x, TuxRect.y, TuxRect.w, TuxRect.h);
+    }
+
+    /* Wait so we keep frame rate constant: */
+    while ((SDL_GetTicks() - start) < 33)
+    {
+      SDL_Delay(20);
+    }
+    frame++;
+  }  // End of while (!finished) loop
+
+  TTF_CloseFont(name_font);
+  FreeSprite(Tux);
+
+  /* Turn off SDL Unicode lookup (because has some overhead): */
+  SDL_EnableUNICODE(SDL_DISABLE);
+
+  /* Now copy name into location pointed to by arg: */ 
+  strncpy((char*)pl_name, (char*)UTF8_buf, HIGH_SCORE_NAME_LENGTH * 3);
+}
+
+
+
+
+/* Zero-out the array before use: */
+void initialize_scores(void)
+{
+  int i, j;
+  for (i = 0; i < NUM_HIGH_SCORE_LEVELS; i++)
+  {
+    for (j = 0; j < HIGH_SCORES_SAVED; j++)
+    {
+      high_scores[i][j].score = 0;
+      strcpy(high_scores[i][j].name, "");
+    }
+  }
+}
+
+/* Test to see where a new score ranks on the list.      */
+/* The return value is the index value - add one to get  */
+/* the common-language place on the list.                */
+int check_score_place(int diff_level, int new_score)
+{
+  int i = 0;
+
+  /* Make sure diff_level is valid: */
+  if (diff_level < 0
+   || diff_level >= NUM_HIGH_SCORE_LEVELS)
+  {
+    fprintf(stderr, "In insert_score(), diff_level invalid!\n");
+    return 0;
+  }
+
+  /* Find correct place in list: */
+  for (i = 0; i < HIGH_SCORES_SAVED; i++)
+  {
+    if (new_score > high_scores[diff_level][i].score)
+      break;
+  }
+
+  return i;  /* So if we return HIGH_SCORES_SAVED, the score did not */
+             /* make the list.                                       */
+}
+
+/* Put a new high score entry into the table for the corresponding */
+/* difficulty level - returns 1 if successful.                     */ 
+int insert_score(char* playername, int diff_level, int new_score)
+{
+  int i = 0;
+  int insert_place;
+
+  insert_place = check_score_place(diff_level, new_score);
+
+  if (HIGH_SCORES_SAVED == insert_place) /* Score didn't make the top 10 */
+  {
+    return 0;
+  }
+
+  /* Move lower entries down: */
+  for (i = HIGH_SCORES_SAVED - 1; i > insert_place; i--)
+  {
+    high_scores[diff_level][i].score =
+            high_scores[diff_level][i - 1].score;
+    strncpy(high_scores[diff_level][i].name,
+            high_scores[diff_level][i - 1].name,
+            HIGH_SCORE_NAME_LENGTH);
+  }
+
+  /* Now put in new entry: */
+  high_scores[diff_level][insert_place].score = new_score;
+  strncpy(high_scores[diff_level][insert_place].name,
+          playername,
+          HIGH_SCORE_NAME_LENGTH);
+  return 1;
+}
+
+
+void print_high_scores(FILE* fp)
+{
+  int i, j;
+
+  fprintf(fp, "\nHigh Scores:\n");
+
+  for (i = 0; i < NUM_HIGH_SCORE_LEVELS; i++)
+  {
+    switch(i)
+    {    
+      case CADET_HIGH_SCORE:
+      {
+        fprintf(fp, "\nSpace Cadet:\n");
+        break;
+      }
+      case SCOUT_HIGH_SCORE:
+      {
+        fprintf(fp, "\nScout:\n");
+        break;
+      }
+      case RANGER_HIGH_SCORE:
+      {
+        fprintf(fp, "\nRanger:\n");
+        break;
+      }
+      case ACE_HIGH_SCORE:
+      {
+        fprintf(fp, "\nAce:\n");
+        break;
+      }
+      case COMMANDO_HIGH_SCORE:
+      {
+        fprintf(fp, "\nCommando:\n");
+        break;
+      }
+      case FACTORS_HIGH_SCORE:
+      {
+        fprintf(fp, "\nFactors:\n");
+        break;
+      }
+      case FRACTIONS_HIGH_SCORE:
+      {
+        fprintf(fp, "\nFractions:\n");
+        break;
+      }
+    }
+
+    for (j = 0; j < HIGH_SCORES_SAVED; j++)
+    {
+      fprintf(fp, "%d.\t%s\t%d\n",
+              j + 1,                  //Convert to common-language ordinals
+              high_scores[i][j].name,
+              high_scores[i][j].score);
+    }
+  }
+}
+
+
+int read_high_scores_fp(FILE* fp)
+{
+  char buf[PATH_MAX];
+  char* token;
+  const char delimiters[] = "\t";
+
+  char* name_read;
+  int score_read;
+  int diff_level;
+
+
+#ifdef TUXMATH_DEBUG
+  printf("\nEntering read_high_scores_fp()\n");
+#endif
+
+  /* get out if file pointer invalid: */
+  if(!fp)
+  {
+    fprintf(stderr, "In read_high_scores_fp(), file pointer invalid!\n");
+    return 0;
+  }
+
+  /* make sure we start at beginning: */
+  rewind(fp);
+
+  /* read in a line at a time: */
+  while (fgets (buf, PATH_MAX, fp))
+  { 
+    /* Ignore comment lines: */
+    if ((buf[0] == ';') || (buf[0] == '#'))
+    {
+      continue;
+    }
+    /* Split up line with strtok()to get needed values,  */ 
+    /* then call insert_score() for each line.           */
+    token = strtok(buf, delimiters);
+    if (!token)
+      continue;
+    diff_level = atoi(token);
+    if (diff_level >= NUM_HIGH_SCORE_LEVELS)
+      continue;
+
+    token = strtok(NULL, delimiters);
+    if (!token)
+      continue; 
+    score_read = atoi(token);
+    /* Note that name can contain spaces - \t is only delimiter: */
+    name_read = strtok(NULL, delimiters);
+    /* Now insert entry: */
+    insert_score(name_read, diff_level, score_read); 
+  }
+  return 1;
+}
+
+
+/* Return the score associated with a table entry:    */
+/* Note: the place is given as the array index, i.e.  */
+/* 0 for the top of the list.                         */
+int HS_Score(int diff_level, int place)
+{
+  /* Make sure diff_level is valid: */
+  if (diff_level < 0
+   || diff_level >= NUM_HIGH_SCORE_LEVELS)
+  {
+    fprintf(stderr, "In HS_Score(), diff_level = %d, invalid!\n", diff_level);
+    return -1;
+  }
+
+  /* Make sure place is valid: */
+  if (place < 0
+   || place >= HIGH_SCORES_SAVED)
+  {
+    fprintf(stderr, "In HS_Score(), place invalid!\n");
+    return -1;
+  }
+
+  return high_scores[diff_level][place].score;
+}
+
+
+/* Return (pointer to) the name associated with a table entry:  */
+char* HS_Name(int diff_level, int place)
+{
+  /* Make sure diff_level is valid: */
+  if (diff_level < 0
+   || diff_level >= NUM_HIGH_SCORE_LEVELS)
+  {
+    fprintf(stderr, "In HS_Score(), diff_level invalid!\n");
+    return NULL;
+  }
+
+  /* Make sure place is valid: */
+  if (place < 0
+   || place >= HIGH_SCORES_SAVED)
+  {
+    fprintf(stderr, "In HS_Score(), place invalid!\n");
+    return NULL;
+  }
+
+  return high_scores[diff_level][place].name;
+}

Added: tuxmath/trunk/src/highscore.h
===================================================================
--- tuxmath/trunk/src/highscore.h	                        (rev 0)
+++ tuxmath/trunk/src/highscore.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,32 @@
+//
+// C Interface: highscore
+//
+// Description: 
+//
+//
+// Author: David Bruce <dbruce at tampabay.rr.com>, (C) 2007
+//
+// Copyright: See COPYING file that comes with this distribution
+// (Briefly, GNU GPL version 2 or greater).
+//
+
+#ifndef HIGHSCORE_H
+#define HIGHSCORE_H
+
+
+#include "globals.h"
+
+void DisplayHighScores(int level);
+void HighScoreNameEntry(char* pl_name);
+void NameEntry(char* pl_name, const char* heading, const char* sub);
+
+int check_score_place(int diff_level, int new_score);
+int insert_score(char* playername, int diff_level, int new_score);
+void initialize_scores(void);
+void print_high_scores(FILE* fp);
+int read_high_scores_fp(FILE* fp);
+/* Note: for writing, use append_high_score in fileops.c */
+
+int HS_Score(int diff_level, int place);
+char* HS_Name(int diff_level, int place);
+#endif

Added: tuxmath/trunk/src/lessons.c
===================================================================
--- tuxmath/trunk/src/lessons.c	                        (rev 0)
+++ tuxmath/trunk/src/lessons.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,145 @@
+/*
+*  C Implementation: lessons
+*
+* Description: 
+*
+*
+* Author: David Bruce <dbruce at tampabay.rr.com>, (C) 2007
+*
+* Copyright: See COPYING file that comes with this distribution
+*
+*/
+#include <stdio.h>
+//for strtok()
+#include <string.h>
+#include "lessons.h"
+//for basename(), if available
+#ifdef HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
+
+// extern unsigned char **lesson_list_titles;
+// extern unsigned char **lesson_list_filenames;
+int* lesson_list_goldstars = NULL;
+// extern int num_lessons;
+
+/* local function prototypes: */
+static int filename_comp(const char* s1, const char* s2);
+
+/* Reads the file pointed to by the arg and sets */
+/* lesson_list_goldstars* accordingly:           */
+int read_goldstars_fp(FILE* fp)
+{
+  char buf[PATH_MAX];
+  char* token;
+  const char delimiters[] = "\t\n\r"; /* this will keep newline chars out of string */
+  int i;
+
+#ifdef TUXMATH_DEBUG
+  printf("\nEntering read_goldstars_fp()\n");
+#endif
+
+  /* get out if file pointer invalid: */
+  if(!fp)
+  {
+    fprintf(stderr, "In read_goldstars_fp(), file pointer invalid!\n");
+    return 0;
+  }
+
+  if (num_lessons <= 0)
+  {
+    perror("no lessons - returning");
+    num_lessons = 0;
+    return 0;
+  }
+
+
+
+  /* make sure we start at beginning: */
+  rewind(fp);
+
+  /* read in a line at a time: */
+  while (fgets (buf, PATH_MAX, fp))
+  { 
+    /* Ignore comment lines: */
+    if ((buf[0] == ';') || (buf[0] == '#'))
+    {
+      continue;
+    }
+
+    /* Split up line with strtok()to get needed values -    */ 
+    /* for now, each line just contains a lesson file name, */
+    /* but eventually there may be more fields (e.g date, % correct) */
+    token = strtok(buf, delimiters);
+    if (!token)
+      continue;
+
+    /* Now set "goldstar" to 1 if we find a matching lesson: */
+    for (i = 0; i < num_lessons; i++)
+    {
+      /* compare basenames only, not entire path (see below): */
+      if (0 == filename_comp(token, lesson_list_filenames[i]))
+      {
+        lesson_list_goldstars[i] = 1;
+        break; //should not have to worry about duplicates
+      }
+    }
+  }
+  return 1;
+}
+
+
+/* Write lessons gold star list to the provided FILE* in format  */
+/* compatible with read_goldstars_fp () above.            */
+
+void write_goldstars_fp(FILE* fp)
+{
+  int i = 0;
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "\nEntering write_goldstars_fp()\n");
+#endif
+
+  /* get out if file pointer invalid: */
+  if(!fp)
+  {
+    fprintf(stderr, "In write_goldstars_fp(), file pointer invalid!\n");
+    return;
+  }
+
+  /* make sure we start at beginning: */
+  rewind(fp);
+
+  for (i = 0; i < num_lessons; i++)
+  {
+#ifdef TUXMATH_DEBUG
+    printf("i = %d\nfilename = %s\ngoldstar = %d\n",
+           i, lesson_list_filenames[i],
+           lesson_list_goldstars[i]);
+#endif
+
+    if(lesson_list_goldstars[i] == 1)
+    {
+      fprintf(fp, "%s\n", lesson_list_filenames[i]);
+    }
+  }
+  return;
+}
+
+
+/* Perform a strcasecmp() on two path strings, stripping away all the */
+/* dirs in the path and just comparing the filenames themselves:      */
+/* FIXME: basename() may not be available on all platforms.           */
+/* If not available, just compare the full paths. Consider including  */
+/* our own implementation at some point. Note that the docs say       */
+/* basename() takes a const char*, but the actual header is char*,    */
+/* hence the casts to reassure the compiler.                          */
+static int filename_comp(const char* s1, const char* s2)
+{
+#ifdef HAVE_BASENAME
+  return strcasecmp(basename((char*)s1), basename((char*)s2));
+#else
+  return strcasecmp(s1, s2);
+#endif
+}
+

Added: tuxmath/trunk/src/lessons.h
===================================================================
--- tuxmath/trunk/src/lessons.h	                        (rev 0)
+++ tuxmath/trunk/src/lessons.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,21 @@
+//
+// C Interface: lessons
+//
+// Description: Code for reading and parsing the lessons directory,
+//              as well as keeping track of the player's progress
+//
+//
+// Author: David Bruce <dbruce at tampabay.rr.com>, (C) 2007
+//
+// Copyright: See COPYING file that comes with this distribution
+// (Briefly, GNU GPL version 2 or greater).
+//
+#ifndef LESSONS_H
+#define LESSONS_H
+
+#include "globals.h"
+
+int read_goldstars_fp(FILE* fp);
+void write_goldstars_fp(FILE* fp);
+
+#endif

Added: tuxmath/trunk/src/linewrap.c
===================================================================
--- tuxmath/trunk/src/linewrap.c	                        (rev 0)
+++ tuxmath/trunk/src/linewrap.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,123 @@
+/* Internationalized line wrapping for TuxMath.
+
+ -  file: linewrap.c
+ -  description: convenience API for libgettextpo's linebreak routine
+                            ------------------
+    begin                : Feb 3 2009
+    copyright            : (C) 2009 by Timothy E. Holy
+    email                : tuxmath-devel at lists.sourceforge.net
+
+***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+//#include <locale.h>
+#include "gettext.h"
+
+#include "../linebreak/linebreak.h"
+#include "linewrap.h"
+
+static char *wrapped_lines0[MAX_LINES];  // for internal storage
+char *wrapped_lines[MAX_LINES];  // publicly available!
+
+void linewrap_initialize()
+{
+  int i;
+
+  for (i = 0; i < MAX_LINES; i++) {
+    wrapped_lines[i] = (char *) malloc(sizeof(char)*MAX_LINEWIDTH);
+    wrapped_lines0[i] = (char *) malloc(sizeof(char)*MAX_LINEWIDTH);
+  }
+}
+
+void linewrap_cleanup()
+{
+  int i;
+
+  for (i = 0; i < MAX_LINES; i++) {
+    free(wrapped_lines[i]);
+    free(wrapped_lines0[i]);
+  }
+}  
+
+
+int linewrap(const char *input,char *str_list[],int width,int max_lines,int max_width)
+{
+  int length = strlen (input);
+  char *breaks = malloc (length);
+  int i;
+  int listIndex;
+  int strIndex;
+  
+  // Generate the positions with line breaks
+  //mbs_width_linebreaks (input, length, width, 0, 0, NULL, locale_charset (), breaks);
+  mbs_width_linebreaks (input, length, width, 0, 0, NULL, "UTF-8", breaks);
+
+  // Load characters into the string list. "breaks" holds "true"
+  // values at the first character of the next line, not at the space
+  // between words.
+  listIndex = 0;
+  for (strIndex = 0, i = 0; i < length; strIndex++,i++) {
+    if (breaks[i] == UC_BREAK_POSSIBLE || breaks[i] == UC_BREAK_MANDATORY) {
+      str_list[listIndex][strIndex] = '\0';  // terminate the previous string
+      strIndex = 0;                          // start the next line
+      listIndex++;
+      if (listIndex >= max_lines)
+	break;
+    }
+    if (strIndex < max_width)
+      str_list[listIndex][strIndex] = input[i];
+  }
+  str_list[listIndex][strIndex] = '\0';
+
+  free(breaks);
+
+  // Return the number of lines
+  if (listIndex < max_lines)
+    return listIndex+1;
+  else
+    return max_lines;
+}
+
+void linewrap_list(const char *input[],char *str_list[],int width,int max_lines,int max_width)
+{
+  int inputIndex;
+  int outputIndex;
+  int intermedIndex;
+  int n_lines;
+
+  outputIndex = 0;
+  for (inputIndex = 0; strlen(input[inputIndex]) > 0 && outputIndex < max_lines-1; inputIndex++) {
+    printf("inputIndex = %d, outputIndex = %d, String: %s\n",inputIndex,outputIndex,input[inputIndex]);
+    /* Handle blank strings */
+    if (strcmp(input[inputIndex], " ") == 0) {
+      strcpy(str_list[outputIndex++]," ");
+      printf("Blank (%d)\n",inputIndex);
+      continue;
+    }
+    /* Handle real strings */
+    printf("Not blank. Translated: %s\n",gettext(input[inputIndex]));
+    n_lines = linewrap(gettext(input[inputIndex]),wrapped_lines0,width,max_lines,max_width);
+    printf("Wrapped to %d lines.\n",n_lines);
+    for (intermedIndex = 0; intermedIndex < n_lines && outputIndex < max_lines-1; intermedIndex++, outputIndex++) {
+      printf("intermedIndex %d, outputIndex %d, string %s\n",intermedIndex,outputIndex,wrapped_lines0[intermedIndex]);
+      strncpy(str_list[outputIndex],wrapped_lines0[intermedIndex],max_width);
+    }
+  }
+  printf("All done (outputIndex = %d)\n",outputIndex);
+  for (; outputIndex < max_lines; outputIndex++) {
+    //printf("  blanking %d\n", outputIndex);
+    str_list[outputIndex][0] = '\0';
+  }
+  printf("All done.\n");
+}

Added: tuxmath/trunk/src/linewrap.h
===================================================================
--- tuxmath/trunk/src/linewrap.h	                        (rev 0)
+++ tuxmath/trunk/src/linewrap.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,37 @@
+#ifndef LINEWRAP_H
+#define LINEWRAP_H
+
+/* linewrap takes an input string (can be in essentially arbitrary
+   encoding) and loads it into an array of strings, each corresponding
+   to one line of output text.  Arguments:
+
+     input: a null-terminated input string
+     str_list: a PRE-ALLOCATED array of character pointers. This must be
+       at least of size str_list[max_lines][max_width]
+     width: the desired number of characters per line. Note that words
+       with more characters than "width" are not hypenated, so it's
+       possible to get a line that is longer than "width."
+     max_lines and max_width: memory-safety parameters for str_list
+       (see above)
+
+   On output, linewrap returns the number of lines used to format the
+   string.
+*/
+extern int linewrap(const char *input, char **str_list, int width, int max_lines, int max_width);
+
+/* This takes a NULL-terminated array of strings and performs
+   translation and linewrapping, outputting another NULL-terminated
+   array. */
+extern void linewrap_list(const char *input[], char *str_list[], int width, int max_lines, int max_width);
+
+
+/* Storage for linewrapping */
+#define MAX_LINES 40
+#define MAX_LINEWIDTH 40
+extern char *wrapped_lines[];
+
+/* Initialization and cleanup of storage */
+void linewrap_initialize();
+void linewrap_cleanup();
+
+#endif

Added: tuxmath/trunk/src/loaders.c
===================================================================
--- tuxmath/trunk/src/loaders.c	                        (rev 0)
+++ tuxmath/trunk/src/loaders.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,413 @@
+/***************************************************************************
+ -  file: loaders.c
+ -  description: Functions to load multimedia for Tux Typing
+                             -------------------
+    begin                : Thu May 4 2000
+    copyright            : (C) 2000 by Sam Hart
+                         : (C) 2003 by Jesse Andrews
+    email                : tuxtype-dev at tux4kids.net
+
+    Modified for use in tuxmath by David Bruce - 2006.
+    email                : <dbruce at tampabay.rr.com>
+                           <tuxmath-devel at lists.sourceforge.net>
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+//#include "globals.h"
+//#include "funcs.h"
+
+#include "tuxmath.h"     // for TUXMATH_DEBUG
+#include "loaders.h"
+#include "setup.h"  // for cleanup_on_error()
+#include "SDL_extras.h"
+
+/* FIXME Doesn't seem to work consistently on all versions of Windows */
+/* check to see if file exists, if so return true */
+// int checkFile( const char *file ) {
+//         static struct stat fileStats;
+// 
+//         fileStats.st_mode = 0;
+// 
+//         stat( file, &fileStats );
+//                 
+//         return (S_IFREG & fileStats.st_mode);
+// }
+
+
+/* Returns 1 if valid file, 2 if valid dir, 0 if neither: */
+int checkFile(const char* file)
+{
+  FILE* fp = NULL;
+  DIR* dp = NULL;
+
+  if (!file)
+  {
+    fprintf(stderr, "CheckFile(): invalid char* argument!");
+    return 0;
+  }
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "CheckFile() - checking: %s\n", file);
+#endif
+
+  dp = opendir(file);
+  if (dp)
+  {
+
+#ifdef TUXMATH_DEBUG
+    fprintf(stderr, "Opened successfully as DIR\n");
+#endif
+
+    closedir(dp);
+    return 2;
+  }
+
+  fp = fopen(file, "r");
+  if (fp)
+  {
+
+#ifdef TUXMATH_DEBUG
+    fprintf(stderr, "Opened successfully as FILE\n");
+#endif
+
+    fclose(fp);
+    return 1;
+  }
+
+  fprintf(stderr, "Unable to open '%s' as either FILE or DIR\n", file);
+  return 0;
+}
+
+
+int max( int n1, int n2 ) {
+  return (n1 > n2 ? n1 : n2);
+}
+
+
+
+/* FIXME: I think we need to provide a single default font with the program data, */
+/* then more flexible code to try to locate or load system fonts. DSB             */
+/* Returns ptr to loaded font if successful, NULL otherwise. */
+TTF_Font* LoadFont(const char* font_name, int font_size)
+{
+  TTF_Font* f;
+  char fontfile[PATH_MAX];
+  sprintf(fontfile, "%s/fonts/%s", DATA_PREFIX, font_name);
+
+  f = TTF_OpenFont(fontfile, font_size);
+
+  /* HACK - better font searching needed! */
+  /* This should mean that font wasn't bundled into data path, which for  */
+  /* now means we are using Debian, so grab from Debian installation loc: */
+  if (!f)
+  { 
+    sprintf(fontfile, "/usr/share/fonts/truetype/ttf-sil-andika/AndikaDesRevG.ttf");
+    f = TTF_OpenFont(fontfile, font_size);
+  }
+
+
+  if (f)
+  {
+#ifdef TUXMATH_DEBUG
+    fprintf(stderr, "LoadFont(): %s loaded successfully\n\n", fontfile);
+#endif
+    return f;
+  }
+  else
+  {
+   fprintf(stderr, "LoadFont(): %s NOT loaded successfully.\n", fontfile);
+   return NULL;
+  }
+}
+
+
+
+/* FIXME checkFile() not working right in Win32 - skipping. */
+/***********************
+        LoadImage : Load an image and set transparent if requested
+************************/
+SDL_Surface* LoadImage( char *datafile, int mode )
+{
+  SDL_Surface* tmp_pic = NULL;
+  SDL_Surface* final_pic = NULL;
+
+  char fn[PATH_MAX];
+
+  sprintf( fn, "%s/images/%s", DATA_PREFIX, datafile );
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "LoadImage(): looking in %s\n", fn);
+#endif
+
+
+  /* Try to load it with SDL_image: */
+  tmp_pic = IMG_Load(fn);
+
+  if (NULL == tmp_pic) /* Could not load image: */
+  {
+    if (mode & IMG_NOT_REQUIRED)
+    { 
+#ifdef TUXMATH_DEBUG
+      fprintf(stderr, "Warning: could not load optional graphics file %s\n", datafile);
+#endif
+      return NULL;  /* Allow program to continue */
+    }
+    /* If image was required, exit from program: */
+    fprintf(stderr, "ERROR could not load required graphics file %s\n", datafile);
+    fprintf(stderr, "%s", SDL_GetError() );
+    cleanup_on_error();
+  }
+
+  /* "else" - now setup the image to the proper format */
+  switch (mode & IMG_MODES)
+  {
+    case IMG_REGULAR:
+    { 
+
+      final_pic = SDL_DisplayFormat(tmp_pic);
+      SDL_FreeSurface(tmp_pic);
+      break;
+    }
+
+    case IMG_ALPHA:
+    {
+
+      final_pic = SDL_DisplayFormatAlpha(tmp_pic);
+      SDL_FreeSurface(tmp_pic);
+      break;
+    }
+
+    case IMG_COLORKEY:
+    {
+
+      SDL_LockSurface(tmp_pic);
+      SDL_SetColorKey(tmp_pic, (SDL_SRCCOLORKEY | SDL_RLEACCEL),
+                      SDL_MapRGB(tmp_pic->format, 255, 255, 0));
+      final_pic = SDL_DisplayFormat(tmp_pic);
+      SDL_FreeSurface(tmp_pic);
+      break;
+    }
+
+    default:
+    {
+#ifdef TUXMATH_DEBUG
+      fprintf(stderr, "Image mode not recognized\n");
+#endif
+      SDL_FreeSurface(tmp_pic);
+    }
+  }
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "Leaving LoadImage()\n\n");
+#endif
+  return final_pic;
+}
+
+/***********************
+        LoadBkgd() : a wrapper for LoadImage() that scales the
+        image to the size of the screen using zoom(), taken
+        from TuxPaint
+************************/
+SDL_Surface* LoadBkgd(char* datafile)
+{
+  SDL_Surface* orig;
+  orig = IMG_Load(datafile);
+
+  if (!orig)
+  {
+    tmdprintf("In LoadBkgd(), LoadImage() returned NULL on %s\n",
+              datafile);
+    return NULL;
+  }
+
+  if ((orig->w == screen->w)
+   && (orig->h == screen->h))
+  {
+    tmdprintf("No zoom required - return bkgd as is\n");
+    return orig;
+  }
+  else
+  { 
+    tmdprintf("Image is %dx%d\n", orig->w, orig->h);
+    tmdprintf("Screen is %dx%d\n", screen->w, screen->h);
+    tmdprintf("Calling zoom() to rescale\n");
+    return zoom(orig, screen->w, screen->h);
+  }
+}
+
+/**********************
+LoadBothBkgds() : loads two scaled images: one for the user's native 
+resolution and one for 640x480 fullscreen. 
+Returns: the number of images that were scaled
+Now we also optimize the format for best performance
+**********************/
+int LoadBothBkgds(char* datafile, SDL_Surface** fs_bkgd, SDL_Surface** win_bkgd)
+{
+  int ret = 0;
+  SDL_Surface* orig = NULL;
+  SDL_Surface* tmp = NULL;
+
+  tmdprintf("Entering LoadBothBkgds()\n");
+  orig = LoadImage(datafile, IMG_REGULAR);
+  tmdprintf("Scaling %dx%d to: %dx%d, %dx%d\n", 
+           orig->w, orig->h, RES_X, RES_Y, fs_res_x, fs_res_y);
+  if (orig->w == RES_X && orig->h == RES_Y)
+  {
+    *win_bkgd = orig;
+  }
+  else
+  {
+    *win_bkgd = zoom(orig, RES_X, RES_Y);
+    ++ret;
+  }
+  
+  if (orig->w == fs_res_x && orig->h == fs_res_y)
+  {
+    *fs_bkgd = orig;
+  }
+  else
+  {
+    *fs_bkgd = zoom(orig, fs_res_x, fs_res_y);
+    ++ret;
+  }
+  
+  if (ret == 2) //orig won't be used at all
+    SDL_FreeSurface(orig);
+
+  // Optimize images before we leave:
+  // turn off transparency, since it's the background:
+  if (*fs_bkgd)  //avoid segfault...
+  {
+    SDL_SetAlpha(*fs_bkgd, SDL_RLEACCEL,SDL_ALPHA_OPAQUE);
+    tmp = SDL_DisplayFormat(*fs_bkgd);  // optimize the format
+    SDL_FreeSurface(*fs_bkgd);
+    *fs_bkgd = tmp;
+  }
+  if (*win_bkgd)
+  {
+    SDL_SetAlpha(*win_bkgd, SDL_RLEACCEL,SDL_ALPHA_OPAQUE);
+    tmp = SDL_DisplayFormat(*win_bkgd);  // optimize the format
+    SDL_FreeSurface(*win_bkgd);
+    *win_bkgd = tmp;
+  }
+
+  tmdprintf("%d images scaled\nLeaving LoadBothBkgds()\n", ret);
+  return ret;
+}
+
+
+sprite* FlipSprite( sprite *in, int X, int Y ) {
+  sprite *out;
+
+  out = malloc(sizeof(sprite));
+  if (in->default_img != NULL)
+          out->default_img = Flip( in->default_img, X, Y );
+  else
+          out->default_img = NULL;
+  for ( out->num_frames=0; out->num_frames<in->num_frames; out->num_frames++ )
+          out->frame[out->num_frames] = Flip( in->frame[out->num_frames], X, Y );
+  out->cur = 0;
+  return out;
+}
+
+
+sprite* LoadSprite( char* name, int MODE ) {
+  sprite *new_sprite;
+  char fn[PATH_MAX];
+  int x;
+
+  /* JA --- HACK check out what has changed with new code */
+
+  new_sprite = malloc(sizeof(sprite));
+
+  sprintf(fn, "%sd.png", name);  // The 'd' means the default image
+  new_sprite->default_img = LoadImage( fn, MODE|IMG_NOT_REQUIRED );
+  for (x = 0; x < MAX_SPRITE_FRAMES; x++) {
+          sprintf(fn, "%s%d.png", name, x);
+          new_sprite->frame[x] = LoadImage( fn, MODE|IMG_NOT_REQUIRED );
+          if ( new_sprite->frame[x] == NULL ) {
+                  new_sprite->cur = 0;
+                  new_sprite->num_frames = x;
+                  break;
+          }
+  }
+
+
+  
+  return new_sprite;
+}
+
+
+
+void FreeSprite( sprite *gfx ) {
+  int x;
+  if (!gfx)
+    return;
+  printf("Freeing image");
+  tmdprintf(" at %p", gfx);
+  for (x = 0; x < gfx->num_frames; x++)
+  {
+    printf(".");
+    SDL_FreeSurface( gfx->frame[x] );
+  }              
+  SDL_FreeSurface( gfx->default_img );
+  printf("done\n");
+  free(gfx);
+}
+
+void next_frame(sprite* s)
+{
+  if (s && s->num_frames)
+    s->cur = (s->cur + 1) % s->num_frames;
+}
+
+/***************************
+        LoadSound : Load a sound/music patch from a file.
+****************************/
+Mix_Chunk* LoadSound( char *datafile )
+{ 
+  Mix_Chunk* tempChunk = NULL;
+  char fn[PATH_MAX];
+
+//    sprintf(fn , "%s/sounds/%s", realPath[i], datafile);
+  sprintf(fn , "%s/sounds/%s", DATA_PREFIX, datafile);
+  tempChunk = Mix_LoadWAV(fn);
+  if (!tempChunk)
+  {
+    fprintf(stderr, "LoadSound(): %s not found\n\n", fn);
+  }
+  return tempChunk;
+}
+
+/************************
+        LoadMusic : Load
+        music from a datafile
+*************************/
+Mix_Music *LoadMusic(char *datafile )
+{ 
+  char fn[PATH_MAX];
+  Mix_Music* tempMusic = NULL;
+
+  sprintf( fn , "%s/sounds/%s", DATA_PREFIX, datafile );
+  if (1 != checkFile(fn))
+  {
+    fprintf(stderr, "LoadMusic(): %s not found\n\n", fn);
+    return NULL;
+  }
+
+  tempMusic = Mix_LoadMUS(fn);
+
+  if (!tempMusic)
+  {
+    fprintf(stderr, "LoadMusic(): %s not loaded successfully\n", fn);
+    printf("Error was: %s\n\n", Mix_GetError());
+  }
+  return tempMusic;
+}

Added: tuxmath/trunk/src/loaders.h
===================================================================
--- tuxmath/trunk/src/loaders.h	                        (rev 0)
+++ tuxmath/trunk/src/loaders.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,57 @@
+//
+// C++ Interface: loaders
+//
+// Description: 
+//
+//
+// Author: David Bruce <davidstuartbruce at gmail.com>, (C) 2009
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef LOADERS_C
+#define LOADERS_C
+
+#include "tuxmath.h"
+
+#include <string.h>
+#include <math.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#define MAX_SPRITE_FRAMES 30
+
+#define IMG_REGULAR  0x01
+#define IMG_COLORKEY 0x02
+#define IMG_ALPHA    0x04
+#define IMG_MODES    0x07
+#define IMG_NOT_REQUIRED 0x10
+#define IMG_NO_THEME     0x20
+
+typedef struct {
+  SDL_Surface *frame[MAX_SPRITE_FRAMES];
+  SDL_Surface *default_img;
+  int num_frames;
+  int cur;
+} sprite;
+
+
+/* in loaders.c (from tuxtype): */
+int         checkFile( const char *file );
+TTF_Font*    LoadFont(const char* font_name, int font_size);
+Mix_Chunk*   LoadSound( char* datafile );
+SDL_Surface* LoadImage( char* datafile, int mode );
+SDL_Surface* LoadBkgd(char* datafile);
+int          LoadBothBkgds(char* datafile, 
+                           SDL_Surface** fs_bkgd, 
+                           SDL_Surface** win_bkgd);
+sprite*      LoadSprite( char* name, int MODE );
+sprite*      FlipSprite( sprite* in, int X, int Y );
+void         FreeSprite( sprite* gfx );
+Mix_Music*   LoadMusic( char *datafile );
+void next_frame(sprite* s);
+
+#endif

Added: tuxmath/trunk/src/mathcards.c
===================================================================
--- tuxmath/trunk/src/mathcards.c	                        (rev 0)
+++ tuxmath/trunk/src/mathcards.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,2439 @@
+/*
+*  C Implementation: mathcards.c
+*
+*       Description: implementation of backend for a flashcard-type math game.
+        Developed as an enhancement to Bill Kendrick's "Tux of Math Command"
+        (aka tuxmath).  (If tuxmath were a C++ program, this would be a C++ class).
+        MathCards could be used as the basis for similar games using a different interface.
+
+*
+*
+* Author: David Bruce <dbruce at tampabay.rr.com>, (C) 2005
+*
+* Copyright: See COPYING file that comes with this distribution.  (Briefly, GNU GPL).
+*
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+
+#include "mathcards.h"
+
+/* extern'd constants */
+
+const char* const MC_OPTION_TEXT[NOPTS+1] = {
+"PLAY_THROUGH_LIST",
+"QUESTION_COPIES",
+"REPEAT_WRONGS",
+"COPIES_REPEATED_WRONGS",
+"ALLOW_NEGATIVES",
+"MAX_ANSWER",
+"MAX_QUESTIONS",
+"MAX_FORMULA_NUMS",
+"MIN_FORMULA_NUMS",
+
+"FORMAT_ANSWER_LAST",
+"FORMAT_ANSWER_FIRST",
+"FORMAT_ANSWER_MIDDLE",
+"FORMAT_ADD_ANSWER_LAST",
+"FORMAT_ADD_ANSWER_FIRST",
+"FORMAT_ADD_ANSWER_MIDDLE",
+"FORMAT_SUB_ANSWER_LAST",
+"FORMAT_SUB_ANSWER_FIRST",
+"FORMAT_SUB_ANSWER_MIDDLE",
+"FORMAT_MULT_ANSWER_LAST",
+"FORMAT_MULT_ANSWER_FIRST",
+"FORMAT_MULT_ANSWER_MIDDLE",
+"FORMAT_DIV_ANSWER_LAST",
+"FORMAT_DIV_ANSWER_FIRST",
+"FORMAT_DIV_ANSWER_MIDDLE",
+
+"ADDITION_ALLOWED",
+"SUBTRACTION_ALLOWED",
+"MULTIPLICATION_ALLOWED",
+"DIVISION_ALLOWED",
+"TYPING_PRACTICE_ALLOWED",
+"ARITHMETIC_ALLOWED",
+"COMPARISON_ALLOWED",
+
+"MIN_AUGEND",
+"MAX_AUGEND",
+"MIN_ADDEND",
+"MAX_ADDEND",
+
+"MIN_MINUEND",
+"MAX_MINUEND",
+"MIN_SUBTRAHEND",
+"MAX_SUBTRAHEND",
+
+"MIN_MULTIPLIER",
+"MAX_MULTIPLIER",
+"MIN_MULTIPLICAND",
+"MAX_MULTIPLICAND",
+
+"MIN_DIVISOR",
+"MAX_DIVISOR",
+"MIN_QUOTIENT",
+"MAX_QUOTIENT",
+
+"MIN_TYPING_NUM",
+"MAX_TYPING_NUM",
+
+"MIN_COMPARATOR" ,
+"MAX_COMPARATOR" ,
+"MIN_COMPARISAND",
+"MAX_COMPARISAND",
+
+"RANDOMIZE",
+
+"COMPREHENSIVE",
+"AVG_LIST_LENGTH",
+"VARY_LIST_LENGTH",
+
+"END_OF_OPTS"
+};
+
+const int MC_DEFAULTS[] = {
+  1,    //PLAY_THROUGH_LIST
+  1,    //QUESTION_COPIES
+  1,    //REPEAT_WRONGS
+  1,    //COPIES_REPEATED_WRONGS
+  0,    //ALLOW_NEGATIVES
+  999,  //MAX_ANSWER
+  5000, //MAX_QUESTIONS
+  2,    //MAX_FORMULA_NUMS
+  2,    //MIN_FORMULA_NUMS
+        //
+  1,    //FORMAT_ANSWER_LAST
+  0,    //FORMAT_ANSWER_FIRST
+  0,    //FORMAT_ANSWER_MIDDLE
+  1,    //FORMAT_ADD_ANSWER_LAST
+  0,    //FORMAT_ADD_ANSWER_FIRST
+  0,    //FORMAT_ADD_ANSWER_MIDDLE
+  1,    //FORMAT_SUB_ANSWER_LAST
+  0,    //FORMAT_SUB_ANSWER_FIRST
+  0,    //FORMAT_SUB_ANSWER_MIDDLE
+  1,    //FORMAT_MULT_ANSWER_LAST
+  0,    //FORMAT_MULT_ANSWER_FIRST
+  0,    //FORMAT_MULT_ANSWER_MIDDLE
+  1,    //FORMAT_DIV_ANSWER_LAST
+  0,    //FORMAT_DIV_ANSWER_FIRST
+  0,    //FORMAT_DIV_ANSWER_MIDDLE
+        //
+  1,    //ADDITION_ALLOWED
+  1,    //SUBTRACTION_ALLOWED
+  1,    //MULTIPLICATION_ALLOWED
+  1,    //DIVISION_ALLOWED
+
+  0,    //TYPING_PRACTICE_ALLOWED
+  1,    //ARITHMETIC_ALLOWED
+  0,    //COMPARISON_ALLOWED
+        //
+  0,    //MIN_AUGEND
+  12,   //MAX_AUGEND
+  0,    //MIN_ADDEND
+  12,   //MAX_ADDEND
+        //
+  0,    //MIN_MINUEND
+  12,   //MAX_MINUEND
+  0,    //MIN_SUBTRAHEND
+  12,   //MAX_SUBTRAHEND
+        //
+  0,    //MIN_MULTIPLIER
+  12,   //MAX_MULTIPLIER
+  0,    //MIN_MULTIPLICAND
+  12,   //MAX_MULTIPLICAND
+        //
+  0,    //MIN_DIVISOR
+  12,   //MAX_DIVISOR
+  0,    //MIN_QUOTIENT
+  12,   //MAX_QUOTIENT
+        //
+  0,    //MIN_TYPING_NUM
+  12,   //MAX_TYPING_NUM
+        //
+  0,    //MIN_COMPARATOR
+  12,   //MAX_COMPARATOR
+  0,    //MIN_COMPARISAND
+  12,   //MAX_COMPARISAND
+
+  1,    //RANDOMIZE
+
+  0,    //COMPREHENSIVE
+  100,  //AVG_LIST_LENGTH
+  1     //VARY_LIST_LENGTH
+};
+
+
+
+/* "Globals" for mathcards.c: */
+#define PI_VAL 3.1415927
+#define NPRIMES 9
+const int smallprimes[NPRIMES] = {2, 3, 5 ,7, 11, 13, 17, 19, 23};
+const char operchars[4] = "+-*/";
+
+MC_Options* math_opts = 0;
+MC_MathQuestion* question_list = 0;
+MC_MathQuestion* wrong_quests = 0;
+MC_MathQuestion* next_wrong_quest = 0;
+int initialized = 0;
+int quest_list_length = 0;
+int answered_correctly = 0;
+int answered_wrong = 0;
+int questions_pending = 0;
+int unanswered = 0;
+int starting_length = 0;
+int max_formula_size = 0; //max length in chars of a flashcard's formula
+int max_answer_size = 0; //and of its answer
+
+/* For keeping track of timing data */
+float* time_per_question_list = NULL;
+int length_time_per_question_list = 0;
+int length_alloc_time_per_question_list = 0;
+
+const MC_FlashCard DEFAULT_CARD = {NULL,NULL,0,0}; //empty card to signal error
+
+/* "private" function prototypes:                        */
+/*                                                       */
+/* these are for internal use by MathCards only - like   */
+/* the private functions of a C++ class. Declared static */
+/* to give file scope rather than extern scope.          */
+
+static MC_MathQuestion* generate_list(void);
+static void clear_negatives(void);
+//static int validate_question(int n1, int n2, int n3);
+//static MC_MathQuestion* create_node(int n1, int n2, int op, int ans, int f);
+static MC_MathQuestion* create_node_from_card(const MC_FlashCard* flashcard);
+static MC_MathQuestion* insert_node(MC_MathQuestion* first, MC_MathQuestion* current, MC_MathQuestion* new_node);
+static MC_MathQuestion* append_node(MC_MathQuestion* list, MC_MathQuestion* new_node);
+static MC_MathQuestion* remove_node(MC_MathQuestion* first, MC_MathQuestion* n);
+static MC_MathQuestion* delete_list(MC_MathQuestion* list);
+//static int copy_node(MC_MathQuestion* original, MC_MathQuestion* copy);
+static int list_length(MC_MathQuestion* list);
+static int randomize_list(MC_MathQuestion** list);
+
+int comp_randomizer(const void *a, const void *b);
+static MC_MathQuestion* pick_random(int length, MC_MathQuestion* list);
+static int compare_node(MC_MathQuestion* first, MC_MathQuestion* other);
+static int already_in_list(MC_MathQuestion* list, MC_MathQuestion* ptr);
+//static int int_to_bool(int i);
+//static int sane_value(int i);
+//static int abs_value(int i);
+static int log10i(int i);
+static int floatCompare(const void *v1,const void *v2);
+
+static void print_list(FILE* fp,MC_MathQuestion* list);
+void print_vect_list(FILE* fp, MC_MathQuestion** vect, int length);
+
+/* these functions are dead code unless compiling with debug turned on: */
+#ifdef MC_DEBUG
+static void print_card(MC_FlashCard card);
+static void print_counters(void);
+//static MC_MathQuestion* create_node_copy(MC_MathQuestion* other);
+//static MC_FlashCard    create_card_from_node(MC_MathQuestion* node);
+#endif
+
+/* Functions for new mathcards architecture */
+static void free_node(MC_MathQuestion* mq); //wrapper for free() that also frees card
+static MC_FlashCard generate_random_flashcard(void);
+static MC_FlashCard generate_random_ooo_card_of_length(int length, int reformat);
+static void copy_card(const MC_FlashCard* src, MC_FlashCard* dest); //deep copy a flashcard
+static MC_MathQuestion* allocate_node(void); //allocate space for a node
+static int compare_card(const MC_FlashCard* a, const MC_FlashCard* b); //test for identical cards
+static int find_divisor(int a); //return a random positive divisor of a
+static int calc_num_valid_questions(void);
+static MC_MathQuestion* add_all_valid(MC_ProblemType pt, MC_MathQuestion* list, MC_MathQuestion** end_of_list);
+static MC_MathQuestion* find_node(MC_MathQuestion* list, int num);
+
+/*  MC_Initialize() sets up the struct containing all of  */
+/*  settings regarding math questions.  It should be      */
+/*  called before any other function.  Many of the other  */
+/*  functions will not work properly if MC_Initialize()   */
+/*  has not been called. It only needs to be called once, */
+/*  i.e when the program is starting, not at the beginning*/
+/*  of each math game for the player. Returns 1 if        */
+/*  successful, 0 otherwise.                              */
+int MC_Initialize(void)
+{
+  int i;
+
+  mcdprintf("\nEntering MC_Initialize()");
+  /* check flag to see if we did this already */
+  if (initialized)
+  {
+
+    #ifdef MC_DEBUG
+    printf("\nAlready initialized");
+    MC_PrintMathOptions(stdout, 0);
+    printf("\nLeaving MC_Initialize()\n");
+    #endif
+
+    return 1;
+  }
+  math_opts = malloc(sizeof(MC_Options));
+  /* bail out if no struct */
+  if (!math_opts)
+  {
+
+    mcdprintf("\nError: math_opts null or invalid");
+    mcdprintf("\nLeaving MC_Initialize()\n");
+
+    fprintf(stderr, "\nUnable to initialize math_options");
+    return 0;
+  }
+
+  /* set defaults */
+  for (i = 0; i < NOPTS; ++i)
+    {
+    math_opts->iopts[i] = MC_DEFAULTS[i];
+    }
+
+  /* if no negatives to be used, reset any negatives to 0 */
+  if (!math_opts->iopts[ALLOW_NEGATIVES])
+  {
+    clear_negatives();
+  }
+
+  initialized = 1;
+
+  #ifdef MC_DEBUG
+  MC_PrintMathOptions(stdout, 0);
+  printf("\nLeaving MC_Initialize()\n");
+  #endif
+
+  return 1;
+}
+
+
+
+/*  MC_StartGame() generates the list of math questions   */
+/*  based on existing settings. It should be called at    */
+/*  the beginning of each math game for the player.       */
+/*  Returns 1 if resultant list contains 1 or more        */
+/*  questions, 0 if list empty or not generated           */
+/*  successfully.                                         */
+int MC_StartGame(void)
+{
+  mcdprintf("\nEntering MC_StartGame()");
+
+  /* if math_opts not set up yet, initialize it: */
+  if (!initialized)
+  {
+
+    mcdprintf("\nNot initialized - calling MC_Initialize()");
+
+    MC_Initialize();
+  }
+
+  if (!math_opts)
+  {
+    mcdprintf("\nCould not initialize - bailing out");
+    mcdprintf("\nLeaving MC_StartGame()\n");
+
+    return 0;
+  }
+  /* we know math_opts exists if we make it to here */
+  srand(time(NULL));
+
+  /* clear out old lists if starting another game: (if not done already) */
+  delete_list(question_list);
+  question_list = NULL;
+  delete_list(wrong_quests);
+  wrong_quests = NULL;
+
+  /* clear the time list */
+  if (time_per_question_list != NULL) {
+    free(time_per_question_list);
+    time_per_question_list = NULL;
+    length_time_per_question_list = 0;
+    length_alloc_time_per_question_list = 0;
+  }
+
+  /* determine how much space needed for strings, based on user options */
+  max_formula_size = MC_GetOpt(MAX_FORMULA_NUMS)
+                   * (log10i(MC_GLOBAL_MAX) + 4) //sign/operator/spaces
+                   + 1; //question mark for answer
+  max_answer_size = (int)(log10i(MC_GLOBAL_MAX) ) + 2; //negative sign + digit
+
+  mcdprintf("max answer, formula size: %d, %d\n",
+            max_answer_size, max_formula_size);
+  /* set up new list with pointer to top: */
+  question_list = generate_list();
+
+  next_wrong_quest = 0;
+  /* initialize counters for new game: */
+  quest_list_length = list_length(question_list);
+  /* Note: the distinction between quest_list_length and  */
+  /* unanswered is that the latter includes questions     */
+  /* that are currently "in play" by the user interface - */
+  /* it is only decremented when an answer to the question*/
+  /* is received.                                         */
+  unanswered = starting_length = quest_list_length;
+  answered_correctly = 0;
+  answered_wrong = 0;
+  questions_pending = 0;
+
+  #ifdef MC_DEBUG
+  print_counters();
+  #endif
+
+  /* make sure list now exists and has non-zero length: */
+  if (question_list && quest_list_length)
+  {
+    mcdprintf("\nGame set up successfully");
+    mcdprintf("\nLeaving MC_StartGame()\n");
+
+    return 1;
+  }
+  else
+  {
+    mcdprintf("\nGame NOT set up successfully - no valid list");
+    mcdprintf("\nLeaving MC_StartGame()\n");
+
+    return 0;
+  }
+}
+
+/*  MC_StartGameUsingWrongs() is like MC_StartGame(),     */
+/*  but uses the incorrectly answered questions from the  */
+/*  previous game for the question list as a review form  */
+/*  of learning. If there were no wrong answers (or no    */
+/*  previous game), it behaves just like MC_StartGame().  */
+/*  FIXME wonder if it should return a different value if */
+/*  the list is created from settings because there is no */
+/*  valid wrong question list?                            */
+int MC_StartGameUsingWrongs(void)
+{
+  mcdprintf("\nEntering MC_StartGameUsingWrongs()");
+
+  /* Note: if not initialized, control will pass to       */
+  /* MC_StartGame() via else clause so don't need to test */
+  /* for initialization here                              */
+  if (wrong_quests &&
+      list_length(wrong_quests))
+  {
+    mcdprintf("\nNon-zero length wrong_quests list found, will");
+    mcdprintf("\nuse for new game list:");
+
+    /* initialize lists for new game: */
+    delete_list(question_list);
+    if(!randomize_list(&wrong_quests)) {
+      fprintf(stderr, "Error during randomization of wrong_quests!\n");
+      /* Punt on trying wrong question list, just run normal game */
+      return MC_StartGame();
+    }
+    question_list = wrong_quests;
+    wrong_quests = 0;
+    next_wrong_quest = 0;
+   /* initialize counters for new game: */
+    quest_list_length = list_length(question_list);
+    unanswered = starting_length = quest_list_length;
+    answered_correctly = 0;
+    answered_wrong = 0;
+    questions_pending = 0;
+
+    #ifdef MC_DEBUG
+    print_counters();
+    print_list(stdout, question_list);
+    printf("\nLeaving MC_StartGameUsingWrongs()\n");
+    #endif
+
+    return 1;
+  }
+  else /* if no wrong_quests list, go to MC_StartGame()   */
+       /* to set up list based on math_opts               */
+  {
+    mcdprintf("\nNo wrong questions to review - generate list from math_opts\n");
+    mcdprintf("\nLeaving MC_StartGameUsingWrongs()\n");
+
+    return MC_StartGame();
+  }
+}
+
+
+/*  MC_NextQuestion() takes a pointer to an allocated     */
+/*  MC_MathQuestion struct and fills in the fields for    */
+/*  use by the user interface program. It basically is    */
+/*  like taking the next flashcard from the pile. The     */
+/*  node containing the question is removed from the list.*/
+/*  Returns 1 if question found, 0 if list empty/invalid  */
+/*  or if argument pointer is invalid.                    */
+int MC_NextQuestion(MC_FlashCard* fc)
+{
+  mcdprintf("\nEntering MC_NextQuestion()\n");
+
+  /* (so we can free the node after removed from list:) */
+  MC_MathQuestion* ptr;
+  ptr = question_list;
+
+  if (!fc )
+  {
+    fprintf(stderr, "\nNull MC_FlashCard* argument!\n");
+    mcdprintf("\nLeaving MC_NextQuestion()\n");
+    return 0;
+  }
+
+  if (!question_list ||
+/*      !next_question || */
+      !list_length(question_list) )
+  {
+    mcdprintf("\nquestion_list invalid or empty");
+    mcdprintf("\nLeaving MC_NextQuestion()\n");
+
+    return 0;
+  }
+
+  /* 'draw' - copy over the first question */
+  copy_card(&question_list->card, fc);
+
+  /* 'discard' - take first question node out of list and free it */
+  question_list = remove_node(question_list, question_list);
+  free_node(ptr);
+  quest_list_length--;
+  questions_pending++;
+
+  #ifdef MC_DEBUG
+  printf("\nnext question is:");
+  print_card(*fc);
+  print_counters();
+  printf("\n\nLeaving MC_NextQuestion()\n");
+  #endif
+
+  return 1;
+}
+
+/*  MC_AnsweredCorrectly() is how the user interface      */
+/*  tells MathCards that the question has been answered   */
+/*  correctly. Returns 1 if no errors.                    */
+int MC_AnsweredCorrectly(MC_FlashCard* fc)
+{
+  mcdprintf("\nEntering MC_AnsweredCorrectly()");
+
+  if (!fc)
+  {
+    fprintf(stderr, "\nMC_AnsweredCorrectly() passed invalid pointer as argument!\n");
+
+    mcdprintf("\nInvalid MC_FlashCard* argument!");
+    mcdprintf("\nLeaving MC_AnsweredCorrectly()\n");
+
+    return 0;
+  }
+
+  #ifdef MC_DEBUG
+  printf("\nQuestion was:");
+  print_card(*fc);
+  #endif
+
+  answered_correctly++;
+  questions_pending--;
+
+  if (!math_opts->iopts[PLAY_THROUGH_LIST])
+  /* reinsert question into question list at random location */
+  {
+    mcdprintf("\nReinserting question into list");
+
+    MC_MathQuestion* ptr1;
+    MC_MathQuestion* ptr2;
+    /* make new node using values from flashcard */
+    ptr1 = create_node_from_card(fc);
+    /* put it into list */
+    ptr2 = pick_random(quest_list_length, question_list);
+    question_list = insert_node(question_list, ptr2, ptr1);
+    quest_list_length++;
+    /* unanswered does not change - was not decremented when */
+    /* question allocated!                                   */
+  }
+  else
+  {
+    mcdprintf("\nNot reinserting question into list");
+    /* not recycling questions so fewer questions remain:      */
+    unanswered--;
+  }
+
+  #ifdef MC_DEBUG
+  print_counters();
+  printf("\nLeaving MC_AnsweredCorrectly()\n");
+  #endif
+
+  return 1;
+}
+
+/*  MC_NotAnsweredCorrectly() is how the user interface    */
+/*  tells MathCards that the player failed to answer the  */
+/*  question correctly. Returns 1 if no errors.           */
+/*  Note: this gets triggered only if a player's city     */
+/*  gets hit by a question, not if they "miss".           */
+int MC_NotAnsweredCorrectly(MC_FlashCard* fc)
+{
+  mcdprintf("\nEntering MC_NotAnsweredCorrectly()");
+
+  if (!fc)
+  {
+    fprintf(stderr, "\nMC_NotAnsweredCorrectly() passed invalid pointer as argument!\n");
+
+    mcdprintf("\nInvalid MC_FlashCard* argument!");
+    mcdprintf("\nLeaving MC_NotAnsweredCorrectly()\n");
+
+    return 0;
+  }
+
+  #ifdef MC_DEBUG
+  printf("\nQuestion was:");
+  print_card(*fc);
+  #endif
+
+  answered_wrong++;
+  questions_pending--;
+
+  /* add question to wrong_quests list: */
+
+  MC_MathQuestion* ptr1;
+  MC_MathQuestion* ptr2;
+
+  ptr1 = create_node_from_card(fc);
+
+  if (!already_in_list(wrong_quests, ptr1)) /* avoid duplicates */
+  {
+    mcdprintf("\nAdding to wrong_quests list");
+    wrong_quests = append_node(wrong_quests, ptr1);
+  }
+  else /* avoid memory leak */
+  {
+    free(ptr1);
+  }
+
+  /* if desired, put question back in list so student sees it again */
+  if (math_opts->iopts[REPEAT_WRONGS])
+  {
+    int i;
+
+    mcdprintf("\nAdding %d copies to question_list:", math_opts->iopts[COPIES_REPEATED_WRONGS]);
+
+    /* can put in more than one copy (to drive the point home!) */
+    for (i = 0; i < math_opts->iopts[COPIES_REPEATED_WRONGS]; i++)
+    {
+      ptr1 = create_node_from_card(fc);
+      ptr2 = pick_random(quest_list_length, question_list);
+      question_list = insert_node(question_list, ptr2, ptr1);
+      quest_list_length++;
+    }
+    /* unanswered stays the same if a single copy recycled or */
+    /* increases by 1 for each "extra" copy reinserted:       */
+    unanswered += (math_opts->iopts[COPIES_REPEATED_WRONGS] - 1);
+  }
+  else
+  {
+    mcdprintf("\nNot repeating wrong answers\n");
+
+    /* not repeating questions so list gets shorter:      */
+    unanswered--;
+  }
+
+  #ifdef MC_DEBUG
+  print_counters();
+  printf("\nLeaving MC_NotAnswered_Correctly()\n");
+  #endif
+
+  return 1;
+
+}
+
+/* Tells user interface if all questions have been answered correctly! */
+/* Requires that at list contained at least one question to start with */
+/* and that wrongly answered questions have been recycled.             */
+int MC_MissionAccomplished(void)
+{
+  if (starting_length
+    && math_opts->iopts[REPEAT_WRONGS]
+    && !unanswered)
+  {
+    return 1;
+  }
+  else
+  {
+    return 0;
+  }
+}
+
+/*  Returns number of questions left (either in list       */
+/*  or "in play")                                          */
+int MC_TotalQuestionsLeft(void)
+{
+  return unanswered;
+}
+
+/*  Returns number of questions left in list, NOT       */
+/*  including questions currently "in play".            */
+int MC_ListQuestionsLeft(void)
+{
+  return quest_list_length;
+}
+
+
+/*  Store the amount of time a given flashcard was      */
+/*  visible on the screen. Returns 1 if the request     */
+/*  succeeds, 0 otherwise.                              */
+int MC_AddTimeToList(float t)
+{
+  int newsize = 0;
+  float *newlist;
+
+  /* This list will be allocated in an STL-like manner: when the       */
+  /* list gets full, allocate an additional amount of storage equal    */
+  /* to the current size of the list, so that only O(logN) allocations */
+  /* will ever be needed. We therefore have to keep track of 2 sizes:  */
+  /* the allocated size, and the actual number of items currently on   */
+  /* the list.                                                         */
+  if (length_time_per_question_list >= length_alloc_time_per_question_list) {
+    /* The list is full, allocate more space */
+    newsize = 2*length_time_per_question_list;
+    if (newsize == 0)
+      newsize = 100;
+    newlist = realloc(time_per_question_list, newsize*sizeof(float));
+    if (newlist == NULL) {
+      #ifdef MC_DEBUG
+      printf("\nError: allocation for time_per_question_list failed\n");
+      #endif
+      return 0;
+    }
+    time_per_question_list = newlist;
+    length_alloc_time_per_question_list = newsize;
+  }
+
+  /* Append the time to the list */
+  time_per_question_list[length_time_per_question_list++] = t;
+  return 1;
+}
+
+/* Frees heap memory used in program:                   */
+void MC_EndGame(void)
+{
+  delete_list(question_list);
+  question_list = 0;
+  delete_list(wrong_quests);
+  wrong_quests = 0;
+
+  if (math_opts)
+  {
+    free(math_opts);
+    math_opts = 0;
+  }
+
+  free(time_per_question_list);
+  time_per_question_list = NULL;
+  length_alloc_time_per_question_list = 0;
+  length_time_per_question_list = 0;
+
+  initialized = 0;
+}
+
+
+
+/* prints struct to file */
+void MC_PrintMathOptions(FILE* fp, int verbose)
+{
+  int i, vcommentsprimed = 0;
+  //comments when writing out verbose...perhaps they can go somewhere less conspicuous
+  static char* vcomments[NOPTS];
+  if (!vcommentsprimed) //we only want to initialize these once
+  {
+    vcommentsprimed = 1;
+    for (i = 0; i < NOPTS; ++i)
+      vcomments[i] = NULL;
+    vcomments[PLAY_THROUGH_LIST] =
+      "\n############################################################\n"
+      "#                                                          #\n"
+      "#                  General Math Options                    #\n"
+      "#                                                          #\n"
+      "# If 'play_through_list' is true, Tuxmath will ask each    #\n"
+      "# question in an internally-generated list. The list is    #\n"
+      "# generated based on the question ranges selected below.   #\n"
+      "# The game ends when no questions remain.                  #\n"
+      "# If 'play_through_list' is false, the game continues      #\n"
+      "# until all cities are destroyed.                          #\n"
+      "# Default is 1 (i.e. 'true' or 'yes').                     #\n"
+      "#                                                          #\n"
+      "# 'question_copies' is the number of times each question   #\n"
+      "# will be asked. It can be 1 to 10 - Default is 1.         #\n"
+      "#                                                          #\n"
+      "# 'repeat_wrongs' tells Tuxmath whether to reinsert        #\n"
+      "# incorrectly answered questions into the list to be       #\n"
+      "# asked again. Default is 1 (yes).                         #\n"
+      "#                                                          #\n"
+      "# 'copies_repeated_wrongs' gives the number of times an    #\n"
+      "# incorrectly answered question will reappear. Default     #\n"
+      "# is 1.                                                    #\n"
+      "#                                                          #\n"
+      "# The defaults for these values result in a 'mission'      #\n"
+      "# for Tux that is accomplished by answering all            #\n"
+      "# questions correctly with at least one surviving city.    #\n"
+      "############################################################\n\n";
+
+    vcomments[FORMAT_ADD_ANSWER_LAST] =
+      "\n############################################################\n"
+      "# The 'format_<op>_answer_<place>  options control         #\n"
+      "# generation of questions with the answer in different     #\n"
+      "# places in the equation.  i.e.:                           #\n"
+      "#                                                          #\n"
+      "#    format_add_answer_last:    2 + 2 = ?                  #\n"
+      "#    format_add_answer_first:   ? + 2 = 4                  #\n"
+      "#    format_add_answer_middle:  2 + ? = 4                  #\n"
+      "#                                                          #\n"
+      "# By default, 'format_answer_first' is enabled and the     #\n"
+      "# other two formats are disabled.  Note that the options   #\n"
+      "# are not mutually exclusive - the question list may       #\n"
+      "# contain questions with different formats.                #\n"
+      "#                                                          #\n"
+      "# The formats are set independently for each of the four   #\n"
+      "# math operations.                                         #\n"
+      "############################################################\n\n";
+
+    vcomments[ALLOW_NEGATIVES] =
+      "\n############################################################\n"
+      "# 'allow_negatives' allows or disallows use of negative    #\n"
+      "# numbers as both operands and answers.  Default is 0      #\n"
+      "# (no), which disallows questions like:                    #\n"
+      "#          2 - 4 = ?                                       #\n"
+      "# Note: this option must be enabled in order to set the    #\n"
+      "# operand ranges to include negatives (see below). If it   #\n"
+      "# is changed from 1 (yes) to 0 (no), any negative          #\n"
+      "# operand limits will be reset to 0.                       #\n"
+      "############################################################\n\n";
+
+    vcomments[MAX_ANSWER] =
+      "\n############################################################\n"
+      "# 'max_answer' is the largest absolute value allowed in    #\n"
+      "# any value in a question (not only the answer). Default   #\n"
+      "# is 144. It can be set as high as 999.                    #\n"
+      "############################################################\n\n";
+
+    vcomments[MAX_QUESTIONS] =
+      "\n############################################################\n"
+      "# 'max_questions' is limit of the length of the question   #\n"
+      "# list. Default is 5000 - only severe taskmasters will     #\n"
+      "# need to raise it.                                        #\n"
+      "############################################################\n\n";
+
+    vcomments[RANDOMIZE] =
+      "\n############################################################\n"
+      "# If 'randomize' selected, the list will be shuffled       #\n"
+      "# at the start of the game.  Default is 1 (yes).           #\n"
+      "############################################################\n\n";
+
+    vcomments[ADDITION_ALLOWED] =
+      "\n############################################################\n"
+      "#                                                          #\n"
+      "#                 Math Operations Allowed                  #\n"
+      "#                                                          #\n"
+      "# These options enable questions for each of the four math #\n"
+      "# operations.  All are 1 (yes) by default.                 #\n"
+      "############################################################\n\n";
+
+    vcomments[MIN_AUGEND] =
+      "\n############################################################\n"
+      "#                                                          #\n"
+      "#      Minimum and Maximum Values for Operand Ranges       #\n"
+      "#                                                          #\n"
+      "# Operand limits can be set to any integer up to the       #\n"
+      "# value of 'max_answer'.  If 'allow_negatives' is set to 1 #\n"
+      "# (yes), either negative or positive values can be used.   #\n"
+      "# Tuxmath will generate questions for every value in the   #\n"
+      "# specified range. The maximum must be greater than or     #\n"
+      "# equal to the corresponding minimum for any questions to  #\n"
+      "# be generated for that operation.                         #\n"
+      "############################################################\n\n";
+
+  }
+
+
+  mcdprintf("\nEntering MC_PrintMathOptions()\n");
+
+  /* bail out if no struct */
+  if (!math_opts)
+  {
+    fprintf(stderr, "\nMath Options struct does not exist!\n");
+    return;
+  }
+
+  for (i = 0; i < NOPTS; ++i)
+    {
+    if (verbose && vcomments[i] != NULL)
+      fprintf(fp, vcomments[i]);
+    fprintf(fp, "%s = %d\n", MC_OPTION_TEXT[i], math_opts->iopts[i]);
+    }
+  mcdprintf("\nLeaving MC_PrintMathOptions()\n");
+}
+
+
+
+int MC_PrintQuestionList(FILE* fp)
+{
+  if (fp && question_list)
+  {
+    print_list(fp, question_list);
+    return 1;
+  }
+  else
+  {
+    fprintf(stderr, "\nFile pointer and/or question list invalid\n");
+    return 0;
+  }
+}
+
+int MC_PrintWrongList(FILE* fp)
+{
+  if (!fp)
+  {
+    fprintf(stderr, "File pointer invalid\n");
+    return 0;
+  }
+
+  if (wrong_quests)
+  {
+    print_list(fp, wrong_quests);
+  }
+  else
+  {
+    fprintf(fp, "\nNo wrong questions!\n");
+  }
+
+  return 1;
+}
+
+
+int MC_StartingListLength(void)
+{
+  return starting_length;
+}
+
+
+int MC_WrongListLength(void)
+{
+  return list_length(wrong_quests);
+}
+
+int MC_NumAnsweredCorrectly(void)
+{
+  return answered_correctly;
+}
+
+
+int MC_NumNotAnsweredCorrectly(void)
+{
+  return answered_wrong;
+}
+
+
+/* Report the median time per question */
+float MC_MedianTimePerQuestion(void)
+{
+  if (length_time_per_question_list == 0)
+    return 0;
+
+  qsort(time_per_question_list,length_time_per_question_list,sizeof(float),floatCompare);
+  return time_per_question_list[length_time_per_question_list/2];
+}
+
+/* Implementation of "private methods" - (cannot be called from outside
+of this file) */
+
+
+
+/* Resets negative values to zero - used when allow_negatives deselected. */
+void clear_negatives(void)
+{
+  int i;
+  for (i = MIN_AUGEND; i <= MAX_TYPING_NUM; ++i)
+    if (math_opts->iopts[i]< 0)
+      math_opts->iopts[i]= 0;
+}
+
+// /* this is used by generate_list to see if a possible question */
+// /* meets criteria to be added to the list or not:              */
+// int validate_question(int n1, int n2, int n3)
+// {
+//   /* make sure none of values exceeds max_answer using absolute */
+//   /* value comparison:                                          */
+//   if (abs_value(n1) > abs_value(math_opts->iopts[MAX_ANSWER])
+//    || abs_value(n2) > abs_value(math_opts->iopts[MAX_ANSWER])
+//    || abs_value(n3) > abs_value(math_opts->iopts[MAX_ANSWER]))
+//   {
+//     return 0;
+//   }
+//   /* make sure none of values are negative if negatives not allowed: */
+//   if (!math_opts->iopts[ALLOW_NEGATIVES])
+//   {
+//     if (n1 < 0 || n2 < 0 || n3 < 0)
+//     {
+//       return 0;
+//     }
+//   }
+//   return 1;
+// }
+
+#if 0 //this code is probably on the way out...
+/* create a new node and return a pointer to it */
+MC_MathQuestion* create_node(int n1, int n2, int op, int ans, int f)
+{
+  MC_MathQuestion* ptr = NULL;
+
+  ptr = (MC_MathQuestion*)malloc(sizeof(MC_MathQuestion));
+
+  if (!ptr)
+  {
+    fprintf(stderr, "create_node() - malloc() failed!\n");
+    return NULL;
+  }
+
+  ptr->card = MC_AllocateFlashcard();
+  ptr->next = NULL;
+  ptr->previous = NULL;
+
+  snprintf(ptr->card.formula_string, max_formula_size, "%d %c %d = ?",
+           n1, op < MC_NUM_OPERS ? operchars[op] : '\0', n2);
+  snprintf(ptr->card.answer_string, max_formula_size, "%d", ans);
+  ptr->card.difficulty = 25 * (op + 1);
+
+
+  /* ptr should now point to a properly constructed node: */
+  return ptr;
+}
+#endif
+
+MC_MathQuestion* create_node_from_card(const MC_FlashCard* flashcard)
+{
+  MC_MathQuestion* ret = allocate_node();
+  copy_card(flashcard, &(ret->card));
+  return ret;
+}
+
+// /* FIXME take care of strings */
+// /* this one copies the contents, including pointers; both nodes must be allocated */
+// int copy_node(MC_MathQuestion* original, MC_MathQuestion* copy)
+// {
+//   if (!original)
+//   {
+//     fprintf(stderr, "\nIn copy_node(): invalid 'original' pointer arg.\n");
+//     return 0;
+//   }
+//   if (!copy)
+//   {
+//     fprintf(stderr, "\nIn copy_node(): invalid 'copy' pointer arg.\n");
+//     return 0;
+//   }
+// 
+//   copy_card(&(original->card), &(copy->card) );
+// 
+//   copy->next = original->next;
+//   copy->previous = original->previous;
+//   copy->randomizer = original->randomizer;
+//   return 1;
+// }
+
+
+
+
+/* this puts the node into the list AFTER the node pointed to by current */
+/* and returns a pointer to the top of the modified list  */
+MC_MathQuestion* insert_node(MC_MathQuestion* first, MC_MathQuestion* current, MC_MathQuestion* new_node)
+{
+  /* return pointer to list unchanged if new_node doesn't exist*/
+  if (!new_node)
+    return first;
+  /* if current doesn't exist, new_node is first */
+  if (!current)
+  {
+    new_node->previous = 0;
+    new_node->next =0;
+    first = new_node;
+    return first;
+  }
+
+  if (current->next)  /* avoid error if at end of list */
+    current->next->previous = new_node;
+  new_node->next = current->next;
+  current->next = new_node;
+  new_node->previous = current;
+  return first;
+}
+
+
+
+/* adds the new node to the end of the list */
+MC_MathQuestion* append_node(MC_MathQuestion* list, MC_MathQuestion* new_node)
+{
+  MC_MathQuestion* ptr;
+  /* return pointer to list unchanged if new_node doesn't exist*/
+  if (!new_node)
+  {
+    return list;
+  }
+
+  /* if list does not exist, new_node is the first (and only) node */
+  if (!list)
+  {
+    return new_node;
+  }
+  /* otherwise, go to end of list */
+  ptr = list;
+  while (ptr->next)
+  {
+    ptr = ptr->next;
+  }
+
+  ptr->next = new_node;
+  new_node->previous = ptr;
+  new_node->next = 0;
+  return list;
+}
+
+
+
+/* this takes the node out of the list but does not delete it */
+/* and returns a pointer to the top of the modified list  */
+MC_MathQuestion* remove_node(MC_MathQuestion* first, MC_MathQuestion* n)
+{
+  if (!n || !first)
+    return first;
+  /* special case if first node being removed */
+  if (n == first)
+     first = first->next;
+
+  if (n->previous)
+    n->previous->next = n->next;
+  if (n->next)
+      n->next->previous = n->previous;
+  n->previous = 0;
+  n->next = 0;
+  return first;
+}
+
+
+
+/* frees memory for entire list and returns null pointer */
+MC_MathQuestion* delete_list(MC_MathQuestion* list)
+{
+  MC_MathQuestion* tmp_ptr;
+  while (list)
+  {
+    tmp_ptr = list->next;
+    free_node (list);
+    list = tmp_ptr;
+  }
+  return list;
+}
+
+
+
+void print_list(FILE* fp, MC_MathQuestion* list)
+{
+  if (!list)
+  {
+    fprintf(fp, "\nprint_list(): list empty or pointer invalid\n");
+    return;
+  }
+
+  MC_MathQuestion* ptr = list;
+  while (ptr)
+  {
+    fprintf(stderr, "%s\n", ptr->card.formula_string);
+    ptr = ptr->next;
+  }
+}
+
+void print_vect_list(FILE* fp, MC_MathQuestion** vect, int length)
+{
+  if (!vect)
+  {
+    fprintf(fp, "\nprint_vect_list(): list empty or pointer invalid\n");
+    return;
+  }
+
+  int i = 0;
+  mcdprintf("Entering print_vect_list()\n");
+  for(i = 0; i < length; i++)
+    fprintf(fp, "%s\n", vect[i]->card.formula_string);
+
+  mcdprintf("Leaving print_vect_list()\n");
+}
+
+#ifdef MC_DEBUG
+void print_card(MC_FlashCard card)
+{
+  printf("\nprint_card():");
+  printf("formula_string = %s\nanswer_string = %s\ndifficulty = %d\n\n",
+         card.formula_string,
+         card.answer_string,
+         card.difficulty);
+}
+
+/* This sends the values of all "global" counters and the */
+/* lengths of the question lists to stdout - for debugging */
+void print_counters(void)
+{
+  printf("\nquest_list_length = \t%d", quest_list_length);
+  printf("\nlist_length(question_list) = \t%d", list_length(question_list));
+  printf("\nstarting_length = \t%d", starting_length);
+  printf("\nunanswered = \t%d", unanswered);
+  printf("\nanswered_correctly = \t%d", answered_correctly);
+  printf("\nanswered_wrong = \t%d", answered_wrong);
+  printf("\nlist_length(wrong_quests) = \t%d", list_length(wrong_quests));
+  printf("\nquestions_pending = \t%d", questions_pending);
+}
+
+// /* a "copy constructor", so to speak */
+// /* FIXME should properly return newly allocated list if more than one node DSB */
+// MC_MathQuestion* create_node_copy(MC_MathQuestion* other)
+// {
+//   MC_MathQuestion* ret = allocate_node();
+//   if (ret)
+//     copy_card(&(other->card), &(ret->card) );
+//   return ret;
+// }
+// 
+// /* FIXME take care of strings */
+// 
+// MC_FlashCard create_card_from_node(MC_MathQuestion* node)
+// {
+//   MC_FlashCard fc;
+//   if (!node)
+//     return DEFAULT_CARD;
+//   fc = MC_AllocateFlashcard();
+//   copy_card(&(node->card), &fc);
+//   return fc;
+// }
+#endif
+
+int list_length(MC_MathQuestion* list)
+{
+  int length = 0;
+  while (list)
+  {
+    length++;
+    list = list->next;
+  }
+  return length;
+}
+
+
+
+
+
+
+/* This is a new implementation written in an attempt to avoid       */
+/* the O(n^2) performance problems seen with the old randomization   */
+/* function. The list is created as a vector, but is for now still   */
+/* made a linked list to minimize changes needed elsewhere.          */
+/* The argument is a pointer to the top of the old list.  This extra */
+/* level of indirection allows the list to be shuffled "in-place".   */
+/* The function returns 1 if successful, 0 on errors.                */
+
+static int randomize_list(MC_MathQuestion** old_list)
+{
+  MC_MathQuestion* old_tmp = *old_list;
+  MC_MathQuestion** tmp_vect = NULL;
+
+  int i = 0;
+  if (!old_list || !*old_list) //invalid/empty list
+    return 0;
+  
+  int old_length = list_length(old_tmp);
+
+  /* set random seed: */
+  srand(time(0));
+
+
+  /* Allocate vector and set ptrs to nodes in old list: */
+
+  /* Allocate a list of pointers, not space for the nodes themselves: */
+  tmp_vect = (MC_MathQuestion**)malloc(sizeof(MC_MathQuestion*) * old_length);
+  /* Set each pointer in the vector to the corresponding node: */
+  for (i = 0; i < old_length; i++)
+  {
+    tmp_vect[i] = old_tmp;
+    tmp_vect[i]->randomizer = rand();
+    old_tmp = old_tmp->next;
+  }
+
+  /* Now simply sort on 'tmp_vect[i]->randomizer' to shuffle list: */
+  qsort(tmp_vect, old_length,
+        sizeof(MC_MathQuestion*),
+        comp_randomizer);
+
+  /* Re-create pointers to provide linked-list functionality:      */
+  /* (stop at 'old_length-1' because we dereference tmp_vect[i+1]) */
+  for(i = 0; i < old_length - 1; i++)
+  {
+    if (!tmp_vect[i])
+    {
+      fprintf(stderr, "Invalid pointer!\n");
+      return 0;
+    }
+    tmp_vect[i]->next = tmp_vect[i+1];
+    tmp_vect[i+1]->previous = tmp_vect[i];
+  }
+  /* Handle end cases: */
+  tmp_vect[0]->previous = NULL;
+  tmp_vect[old_length-1]->next = NULL;
+
+  /* Now arrange for arg pointer to indirectly point to first element! */
+  *old_list = tmp_vect[0];
+  free(tmp_vect);
+  return 1;
+}
+
+
+
+/* This is needed for qsort(): */
+int comp_randomizer (const void* a, const void* b)
+{
+
+  int int1 = (*(const struct MC_MathQuestion **) a)->randomizer;
+  int int2 = (*(const struct MC_MathQuestion **) b)->randomizer;
+
+  if (int1 > int2)
+    return 1;
+  else if (int1 == int2)
+    return 0;
+  else
+    return -1;
+}
+
+MC_MathQuestion* pick_random(int length, MC_MathQuestion* list)
+{
+  int i;
+  int rand_node;
+
+  /* set random seed DSB */
+  srand(time(0));
+
+  /* if length is zero, get out to avoid divide-by-zero error */
+  if (0 == length)
+  {
+    return list;
+  }
+
+  rand_node = rand() % length;
+
+  for (i=1; i < rand_node; i++)
+  {
+    if (list)
+     list = list->next;
+  }
+
+  return list;
+}
+
+/* compares fields other than pointers */
+int compare_node(MC_MathQuestion* first, MC_MathQuestion* other)
+{
+  if (!first || !other)
+    return 0;
+  if (compare_card(&(first->card), &(first->card) ) ) //cards are equal
+    return 1;
+  else
+    return 0;
+}
+
+/* check to see if list already contains an identical node */
+int already_in_list(MC_MathQuestion* list, MC_MathQuestion* ptr)
+{
+  if (!list || !ptr)
+    return 0;
+
+  while (list)
+  {
+    if (compare_node(list, ptr))
+      return 1;
+    list = list->next;
+  }
+  return 0;
+}
+
+// /* to prevent option settings in math_opts from getting set to */
+// /* values other than 0 or 1                                    */
+// int int_to_bool(int i)
+// {
+//   if (i)
+//     return 1;
+//   else
+//     return 0;
+// }
+
+// /* prevent values from getting into math_opts that are outside */
+// /* the range that can be handled by the program (i.e. more     */
+// /* than three digits; also disallow negatives if that has been */
+// /* selected.                                                   */
+// int sane_value(int i)
+// {
+//   if (i > MC_GLOBAL_MAX)
+//     i = MC_GLOBAL_MAX;
+//   else if (i < -MC_GLOBAL_MAX)
+//     i = -MC_GLOBAL_MAX;
+// 
+//   if (i < 0
+//    && math_opts
+//    && !math_opts->iopts[ALLOW_NEGATIVES])
+//   {
+//     i = 0;
+//   }
+// 
+//   return i;
+// }
+
+// int abs_value(int i)
+// {
+//   if (i > 0)
+//     return i;
+//   else
+//     return -i;
+// }
+
+int log10i(int i) //base 10 logarithm for ints
+{
+  int j;
+  for (j = 0; i > 0; i /= 10, ++j);
+  return j;
+}
+
+/* Compares two floats (needed for sorting in MC_MedianTimePerQuestion) */
+int floatCompare(const void *v1,const void *v2)
+{
+  float f1,f2;
+
+  f1 = *((float *) v1);
+  f2 = *((float *) v2);
+
+  if (f1 < f2)
+    return -1;
+  else if (f1 > f2)
+    return 1;
+  else
+    return 0;
+}
+
+
+
+/****************************************************
+Functions for new mathcards architecture
+****************************************************/
+
+void copy_card(const MC_FlashCard* src, MC_FlashCard* dest)
+{
+  if (!src || !dest)
+    return;
+  mcdprintf("Copying '%s' to '%s', ", src->formula_string,dest->formula_string);
+  mcdprintf("copying '%s' to '%s'\n", src->answer_string, dest->answer_string);
+  strncpy(dest->formula_string, src->formula_string, max_formula_size);
+  strncpy(dest->answer_string, src->answer_string, max_answer_size);
+  mcdprintf("Card is: '%s', '%s'\n", dest->formula_string, dest->answer_string);
+  dest->answer = src->answer;
+  dest->difficulty = src->difficulty;
+}
+
+void free_node(MC_MathQuestion* mq) //no, not that freenode.
+{
+  if (!mq)
+    return;
+  MC_FreeFlashcard(&(mq->card) );
+  free(mq);
+}
+
+MC_MathQuestion* allocate_node()
+{
+  MC_MathQuestion* ret = NULL;
+  ret = malloc(sizeof(MC_MathQuestion) );
+  if (!ret)
+  {
+    printf("Could not allocate space for a new node!\n");
+    return NULL;
+  }
+
+  ret->card = MC_AllocateFlashcard();
+  ret->next = ret->previous = NULL;
+  
+  return ret;
+}
+
+/*
+The function that does the central dirty work pertaining to flashcard
+creation. Extensible to just about any kind of math problem, perhaps
+with the exception of those with multiple answers, such as "8 + 2 > ?"
+Simply specify how the problem is presented to the user, and the
+answer the game should look for, as strings.
+*/
+MC_FlashCard generate_random_flashcard(void)
+{
+  int num;
+  int length;
+  MC_ProblemType pt;
+  MC_FlashCard ret;
+
+  mcdprintf("Entering generate_random_flashcard()\n");
+
+  do
+    pt = rand() % MC_NUM_PTYPES;
+  while ( (pt == MC_PT_TYPING && !MC_GetOpt(TYPING_PRACTICE_ALLOWED) ) ||
+          (pt == MC_PT_ARITHMETIC && !MC_GetOpt(ADDITION_ALLOWED) &&
+                                   !MC_GetOpt(SUBTRACTION_ALLOWED) &&
+                                   !MC_GetOpt(MULTIPLICATION_ALLOWED) &&
+                                   !MC_GetOpt(DIVISION_ALLOWED) ) ||
+          (pt == MC_PT_COMPARISON && !MC_GetOpt(COMPARISON_ALLOWED) )
+        );
+
+  if (pt == MC_PT_TYPING) //typing practice
+  {
+    mcdprintf("Generating typing question\n");
+    ret = MC_AllocateFlashcard();
+    num = rand() % (MC_GetOpt(MAX_TYPING_NUM)-MC_GetOpt(MIN_TYPING_NUM) + 1)
+                  + MC_GetOpt(MIN_TYPING_NUM);
+    snprintf(ret.formula_string, max_formula_size, "%d", num);
+    snprintf(ret.answer_string, max_answer_size, "%d", num);
+    ret.answer = num;
+    ret.difficulty = 10;
+  }
+  else //if (pt == MC_PT_ARITHMETIC)
+  {
+    mcdprintf("Generating arithmetic question");
+    length = rand() % (MC_GetOpt(MAX_FORMULA_NUMS) -
+                       MC_GetOpt(MIN_FORMULA_NUMS) + 1) //avoid div by 0
+                    +  MC_GetOpt(MIN_FORMULA_NUMS);
+    mcdprintf(" of length %d", length);
+    ret = generate_random_ooo_card_of_length(length, 1);
+    #ifdef MC_DEBUG
+    print_card(ret);
+    #endif
+  }
+  //TODO comparison problems (e.g. "6 ? 9", "<")
+
+  mcdprintf("Exiting generate_random_flashcard()\n");
+
+  return ret;
+}
+
+/*
+Recursively generate an order of operations problem. Hopefully this won't
+raise performance issues. Difficulty is calculated based on the length of
+the formula and on the operators used. Problems have a 'base' difficulty of
+1 for binary operations, 3 for 3 numbers, 6, 10, etc. Each operator adds to
+the score: 0, 1, 2, and 3 respectively for addition, subtraction,
+multiplication and division.If reformat is 0, FORMAT_ANS_LAST will be used,
+otherwise a format is chosen at random.
+*/
+MC_FlashCard generate_random_ooo_card_of_length(int length, int reformat)
+{
+  int format = 0;
+  int r1 = 0;
+  int r2 = 0;
+  int ans = 0;
+  char tempstr[max_formula_size];
+  MC_FlashCard ret;
+  MC_Operation op;
+
+  printf(".");
+  if (length > MAX_FORMULA_NUMS)
+    return DEFAULT_CARD;
+  if (length <= 2)
+  {
+    mcdprintf("\n");
+    ret = MC_AllocateFlashcard();
+    for (op = rand() % MC_NUM_OPERS; //pick a random operation
+         MC_GetOpt(op + ADDITION_ALLOWED) == 0; //make sure it's allowed
+         op = rand() % MC_NUM_OPERS);
+
+    mcdprintf("Operation is %c\n", operchars[op]);
+    /*
+    if (op == MC_OPER_ADD)
+    {
+      r1 = rand() % (math_opts->iopts[MAX_AUGEND] - math_opts->iopts[MIN_AUGEND] + 1) + math_opts->iopts[MIN_AUGEND];
+      r2 = rand() % (math_opts->iopts[MAX_ADDEND] - math_opts->iopts[MIN_ADDEND] + 1) + math_opts->iopts[MIN_ADDEND];
+      ans = r1 + r2;
+    }
+    else if (op == MC_OPER_SUB)
+    {
+      r1 = rand() % (math_opts->iopts[MAX_MINUEND] - math_opts->iopts[MIN_MINUEND] + 1) + math_opts->iopts[MIN_MINUEND];
+      r2 = rand() % (math_opts->iopts[MAX_SUBTRAHEND] - math_opts->iopts[MIN_SUBTRAHEND] + 1) + math_opts->iopts[MIN_SUBTRAHEND];
+      ans = r1 - r2;
+    }
+    else if (op == MC_OPER_MULT)
+    {
+      r1 = rand() % (math_opts->iopts[MAX_MULTIPLIER] - math_opts->iopts[MIN_MULTIPLIER] + 1) + math_opts->iopts[MIN_MULTIPLIER];
+      r2 = rand() % (math_opts->iopts[MAX_MULTIPLICAND] - math_opts->iopts[MIN_MULTIPLICAND] + 1) + math_opts->iopts[MIN_MULTIPLICAND];
+      ans = r1 * r2;
+    }
+    else if (op == MC_OPER_DIV)
+    {
+      ans = rand() % (math_opts->iopts[MAX_QUOTIENT] - math_opts->iopts[MIN_QUOTIENT] + 1) + math_opts->iopts[MIN_QUOTIENT];
+      r2 = rand() % (math_opts->iopts[MAX_DIVISOR] - math_opts->iopts[MIN_DIVISOR] + 1) + math_opts->iopts[MIN_DIVISOR];
+      if (r2 == 0)
+        r2 = 1;
+      r1 = ans * r2;
+    }
+    */
+    if (op > MC_OPER_DIV || op < MC_OPER_ADD)
+    {
+      mcdprintf("Invalid operator: value %d\n", op);
+      return DEFAULT_CARD;
+    }
+    //choose two numbers in the proper range and get their result
+    
+    else do
+    {
+      r1 = rand() % (math_opts->iopts[MAX_AUGEND+4*op] - math_opts->iopts[MIN_AUGEND+4*op] + 1) + math_opts->iopts[MIN_AUGEND+4*op];    
+      r2 = rand() % (math_opts->iopts[MAX_ADDEND+4*op] - math_opts->iopts[MIN_ADDEND+4*op] + 1) + math_opts->iopts[MIN_ADDEND+4*op]; 
+
+      if (op == MC_OPER_ADD)
+        ans = r1 + r2;
+      if (op == MC_OPER_SUB)
+        ans = r1 - r2;
+      if (op == MC_OPER_MULT)
+        ans = r1 * r2;
+      if (op == MC_OPER_DIV)  
+      {
+        if (r2 == 0)
+          r2 = 1;
+        ret.difficulty = r1;
+        r1 *= r2;
+        ans = ret.difficulty;
+      }
+    } while ( (ans < 0 && !MC_GetOpt(ALLOW_NEGATIVES)) || ans > MC_GetOpt(MAX_ANSWER) );
+
+
+    mcdprintf("Constructing answer_string\n");
+    snprintf(ret.answer_string, max_answer_size+1, "%d", ans);
+    mcdprintf("Constructing formula_string\n");
+    snprintf(ret.formula_string, max_formula_size, "%d %c %d",
+             r1, operchars[op], r2);
+    ret.answer = ans;
+    ret.difficulty = op + 1;
+
+  }
+  else //recurse
+  {
+    ret = generate_random_ooo_card_of_length(length - 1, 0);
+
+    if (strchr(ret.formula_string, '+') || strchr(ret.formula_string, '-') )
+    {
+      //if the expression has addition or subtraction, we can't assume that
+      //introducing multiplication or division will produce a predictable
+      //result, so we'll limit ourselves to more addition/subtraction
+      for (op = rand() % 2 ? MC_OPER_ADD : MC_OPER_SUB;
+           MC_GetOpt(op + ADDITION_ALLOWED) == 0;
+           op = rand() % 2 ? MC_OPER_ADD : MC_OPER_SUB);
+
+    }
+    else
+    {
+      //the existing expression can be treated as a number in itself, so we
+      //can do anything to it and be confident of the result.
+      for (op = rand() % MC_NUM_OPERS; //pick a random operation
+         MC_GetOpt(op + ADDITION_ALLOWED) == 0; //make sure it's allowed
+         op = rand() % MC_NUM_OPERS);
+    }
+    mcdprintf("Next operation is %c,",  operchars[op]);
+
+    //pick the next operand
+    if (op == MC_OPER_ADD)
+    {
+      r1 = rand() % (math_opts->iopts[MAX_AUGEND] - math_opts->iopts[MIN_AUGEND] + 1) + math_opts->iopts[MIN_AUGEND];
+      ret.answer += r1;
+    }
+    else if (op == MC_OPER_SUB)
+    {
+      r1 = rand() % (math_opts->iopts[MAX_SUBTRAHEND] - math_opts->iopts[MIN_SUBTRAHEND] + 1) + math_opts->iopts[MIN_SUBTRAHEND];
+      ret.answer -= r1;
+    }
+    else if (op == MC_OPER_MULT)
+    {
+      r1 = rand() % (math_opts->iopts[MAX_MULTIPLICAND] - math_opts->iopts[MIN_MULTIPLICAND] + 1) + math_opts->iopts[MIN_AUGEND];
+      ret.answer *= r1;
+    }
+    else if (op == MC_OPER_DIV)
+    {
+      r1 = find_divisor(ret.answer);
+      ret.answer /= r1;
+    }
+    else
+    {
+      ; //invalid operator
+    }
+    mcdprintf(" operand is %d\n", r1);
+    mcdprintf("Answer: %d\n", ret.answer);
+
+    //next append or prepend the new number (might need optimization)
+    if (op == MC_OPER_SUB || op == MC_OPER_DIV || //noncommutative, append only
+        rand() % 2)
+    {
+      snprintf(tempstr, max_formula_size, "%s %c %d", //append
+               ret.formula_string, operchars[op], r1);
+      strncpy(ret.formula_string, tempstr, max_formula_size);
+    }
+    else //we're prepending
+    {
+      snprintf(tempstr, max_formula_size, "%d %c %s", //append
+               r1, operchars[op], ret.formula_string);
+      strncpy(ret.formula_string, tempstr, max_formula_size);
+    }
+
+    //finally update the answer and score
+    snprintf(ret.answer_string, max_answer_size, "%d", ret.answer);
+    ret.difficulty += (length - 1) + op;
+  }
+  
+  if (reformat)
+  {
+    mcdprintf("Reformatting...\n");
+    do {
+      format = rand() % MC_NUM_FORMATS;
+    } while (!MC_GetOpt(FORMAT_ANSWER_LAST + format) && 
+             !MC_GetOpt(FORMAT_ADD_ANSWER_LAST + op * 3 + format) );
+   
+    strncat(ret.formula_string, " = ?", max_formula_size - strlen(ret.formula_string) );
+    reformat_arithmetic(&ret, format );     
+  }
+  return ret;
+}
+
+
+
+MC_MathQuestion* generate_list(void)
+{
+  int i, j;
+  int length = MC_GetOpt(AVG_LIST_LENGTH);
+  int cl; //raw length
+  double r1, r2, delta, var; //randomizers for list length
+  MC_MathQuestion* list = NULL;
+  MC_MathQuestion* end_of_list = NULL;
+  MC_MathQuestion* tnode = NULL;
+
+  MC_PrintMathOptions(stdout, 0);
+  if (!(MC_GetOpt(ARITHMETIC_ALLOWED) ||
+      MC_GetOpt(TYPING_PRACTICE_ALLOWED) ||
+      MC_GetOpt(COMPARISON_ALLOWED) ) )
+    return NULL;
+
+  //randomize list length by a "bell curve" centered on average
+  if (length && MC_GetOpt(VARY_LIST_LENGTH) )
+  {
+    r1 = (double)rand() / RAND_MAX / 2 + 0.5; //interval (0, 1)
+    r2 = (double)rand() / RAND_MAX / 2 + 0.5; //interval (0, 1)
+    mcdprintf("Randoms chosen: %5f, %5f\n", r1, r2);
+    delta = sqrt(-2 * log(r1) ) * cos(2 * PI_VAL * r2); //standard normal dist.
+    var = length / 10.0; //variance
+    delta = delta * var;
+    mcdprintf("Delta of average is %5f\n", delta);
+    length += delta;
+    if (length < 0)
+      length = 1; //just in case...
+  }
+
+  if (MC_GetOpt(COMPREHENSIVE)) //generate all
+  {
+    int num_valid_questions; //How many questions the COMPREHENSIVE list specifies
+    int cycles_needed;       //How many times we need to generate it to get enough
+
+    num_valid_questions = calc_num_valid_questions();
+    if(num_valid_questions == 0)
+    {
+      fprintf(stderr, "generate_list() - no valid questions\n");
+      return NULL;
+    }
+
+    cycles_needed = length/num_valid_questions;
+
+    if((cycles_needed * num_valid_questions) < length)
+      cycles_needed++;
+
+    mcdprintf("In generate_list() - COMPREHENSIVE method requested\n");
+    mcdprintf("num_valid_questions = %d\t cycles_needed = %d\n",
+              num_valid_questions, cycles_needed);
+
+    for (i = MC_PT_TYPING; i < MC_NUM_PTYPES; ++i)
+    {
+      if (!MC_GetOpt(i + TYPING_PRACTICE_ALLOWED))
+          continue;
+      for (j = 0; j < cycles_needed; j++)
+        list = add_all_valid(i, list, &end_of_list);
+    }
+
+
+    if (MC_GetOpt(RANDOMIZE) )
+    {
+      mcdprintf("Randomizing list\n");
+      randomize_list(&list);
+    }
+
+    if (length)
+    {
+      cl = list_length(list);
+      // NOTE this should no longer happen - we run the COMPREHENSIVE
+      // generation until we have enough questions.
+      if (length > cl) //if not enough questions, pad out with randoms
+      {
+        mcdprintf("Padding out list from %d to %d questions\n", cl, length);
+        for (i = cl; i < length; ++i)
+        {
+          tnode = malloc(sizeof(MC_MathQuestion) );
+          if(!tnode)
+          {
+            fprintf(stderr, "In generate_list() - allocation failed!\n");
+            delete_list(list);
+            return NULL;
+          }
+
+          tnode->card = generate_random_flashcard();
+          list = insert_node(list, end_of_list, tnode);
+          end_of_list = tnode;
+          mcdprintf("%d...", list_length(list) );
+        }
+      }
+      else if (length < cl) //if too many questions, chop off tail end of list
+      {
+        mcdprintf("Cutting list to %d questions\n", length);
+        end_of_list = find_node(list, length);
+        delete_list(end_of_list->next);
+        end_of_list->next = NULL;
+      }
+    }
+  }
+
+  /* Here we are just generating random questions, one at a */
+  /* time until we have enough                              */
+  else 
+  {
+    mcdprintf("In generate_list() - COMPREHENSIVE method NOT requested\n");
+
+    for (i = 0; i < length; ++i)
+    {
+      tnode = malloc(sizeof(MC_MathQuestion) );
+      if(!tnode)
+      {
+        fprintf(stderr, "In generate_list() - allocation failed!\n");
+        delete_list(list);
+        return NULL;
+      }
+
+      tnode->card = generate_random_flashcard();
+      list = insert_node(list, end_of_list, tnode);
+      end_of_list = tnode;
+    }
+  }
+  return list;
+}
+
+static int compare_card(const MC_FlashCard* a, const MC_FlashCard* b)
+{
+  if (strncmp(a->formula_string, b->formula_string, max_formula_size) )
+    return 1;
+  if (strncmp(a->answer_string, b->answer_string, max_answer_size) )
+    return 1;
+  if (a->answer != b->answer);
+    return 1;
+
+  return 0; //the cards are identical
+}
+
+/* Public functions */
+
+/* allocate space for an MC_Flashcard */
+MC_FlashCard MC_AllocateFlashcard(void)
+{
+  MC_FlashCard ret;
+  mcdprintf("Allocating %d + %d bytes for flashcard\n",
+            max_formula_size + 1, max_answer_size + 1);
+  ret.formula_string = malloc( (max_formula_size + 1) * sizeof(char));
+  ret.answer_string = malloc( (max_answer_size + 1) * sizeof(char));
+  if (!ret.formula_string || !ret.answer_string)
+    {
+    free(ret.formula_string);
+    free(ret.answer_string);
+    printf("Couldn't allocate space for a new flashcard!\n");
+    ret = DEFAULT_CARD;
+    }
+  return ret;
+}
+
+void MC_FreeFlashcard(MC_FlashCard* fc)
+{
+  if (!fc)
+    return;
+//  mcdprintf("Freeing formula_string\n");
+  if (fc->formula_string)
+  {
+    free(fc->formula_string);
+    fc->formula_string = NULL;
+  }
+//  mcdprintf("Freeing answer_string\n");
+  if (fc->answer_string)
+  {
+    free(fc->answer_string);
+    fc->answer_string = NULL;
+  }
+}
+
+unsigned int MC_MapTextToIndex(const char* text)
+{
+  int i;
+  for (i = 0; i < NOPTS; ++i)
+  {
+    if (!strcasecmp(text, MC_OPTION_TEXT[i]) )
+      return i;
+  }
+  mcdprintf("'%s' isn't a math option\n", text);
+  return NOT_VALID_OPTION;
+}
+
+
+//TODO more intuitive function names for access by index vs. by text
+void MC_SetOpt(unsigned int index, int val)
+{
+  if (index >= NOPTS)
+  {
+    mcdprintf("Invalid math option index: %d\n", index);
+    return;
+  }
+
+  /* Do some sanity checks before we throw val into the struct: */
+  switch(index)
+  {
+    /* All the booleans must be 0 or 1: */
+    case PLAY_THROUGH_LIST:
+    case REPEAT_WRONGS:
+    case ALLOW_NEGATIVES:
+    case FORMAT_ANSWER_LAST:
+    case FORMAT_ANSWER_FIRST:
+    case FORMAT_ANSWER_MIDDLE:
+    case FORMAT_ADD_ANSWER_LAST:
+    case FORMAT_ADD_ANSWER_FIRST:
+    case FORMAT_ADD_ANSWER_MIDDLE:
+    case FORMAT_SUB_ANSWER_LAST:
+    case FORMAT_SUB_ANSWER_FIRST:
+    case FORMAT_SUB_ANSWER_MIDDLE:
+    case FORMAT_MULT_ANSWER_LAST:
+    case FORMAT_MULT_ANSWER_FIRST:
+    case FORMAT_MULT_ANSWER_MIDDLE:
+    case FORMAT_DIV_ANSWER_LAST:
+    case FORMAT_DIV_ANSWER_FIRST:
+    case FORMAT_DIV_ANSWER_MIDDLE:
+    case ADDITION_ALLOWED:
+    case SUBTRACTION_ALLOWED:
+    case MULTIPLICATION_ALLOWED:
+    case DIVISION_ALLOWED:
+    case TYPING_PRACTICE_ALLOWED:
+    case ARITHMETIC_ALLOWED:
+    case COMPARISON_ALLOWED:
+    case RANDOMIZE:
+    case COMPREHENSIVE:
+    case VARY_LIST_LENGTH:
+    {
+      /* Reset all non-zero values to one: */
+      if(val)
+      {
+        if(val != 1)
+        {
+          fprintf(stderr, "Warning - parameter %s with invalid value %d, "
+                          "resetting to 1\n", MC_OPTION_TEXT[index], val);
+          val = 1;
+        }
+      }
+      break;
+    }
+
+    /* Parameters concerning numbers of questions */
+    /* must be greater than or equal to zero:     */
+    /* TODO some additional checks would make sense */
+    case QUESTION_COPIES:
+    case COPIES_REPEATED_WRONGS:
+    case MAX_QUESTIONS:
+    case MAX_FORMULA_NUMS:
+    case MIN_FORMULA_NUMS:
+    case AVG_LIST_LENGTH:
+    {
+      /* Reset all negative values to zero: */
+      if(val < 0)
+      {
+        fprintf(stderr, "Warning - parameter %s with invalid value %d, "
+                        "resetting to 0\n", MC_OPTION_TEXT[index], val);
+        val = 0;
+      }
+      break;
+    }
+
+    /* Operand values - make sure they are in displayable range */
+    /* i.e. -999 to 999                                         */ 
+    case MAX_ANSWER:
+    case MIN_AUGEND:
+    case MAX_AUGEND:
+    case MIN_ADDEND:
+    case MAX_ADDEND:
+    case MIN_MINUEND:
+    case MAX_MINUEND:
+    case MIN_SUBTRAHEND:
+    case MAX_SUBTRAHEND:
+    case MIN_MULTIPLIER:
+    case MAX_MULTIPLIER:
+    case MIN_MULTIPLICAND:
+    case MAX_MULTIPLICAND:
+    case MIN_DIVISOR:
+    case MAX_DIVISOR:
+    case MIN_QUOTIENT:
+    case MAX_QUOTIENT:
+    case MIN_TYPING_NUM:
+    case MAX_TYPING_NUM:
+    case MIN_COMPARATOR:
+    case MAX_COMPARATOR:
+    case MIN_COMPARISAND:
+    case MAX_COMPARISAND:
+    {
+      if(val > MC_GLOBAL_MAX)
+      {
+        fprintf(stderr, "Warning - parameter %s with invalid value %d, "
+                       "resetting to %d\n", MC_OPTION_TEXT[index],
+                       val, MC_GLOBAL_MAX);
+        val = MC_GLOBAL_MAX;
+      }
+
+      if(val < (0 - MC_GLOBAL_MAX))
+      {
+        fprintf(stderr, "Warning - parameter %s with invalid value %d, "
+                        "resetting to %d\n", MC_OPTION_TEXT[index],
+                       val, (0 - MC_GLOBAL_MAX));
+        val = (0 - MC_GLOBAL_MAX);
+      }
+
+      break;
+    }
+
+    default:
+        fprintf(stderr, "Warning - in MC_SetOpt() - unrecognized index %d\n",
+                index);
+  }
+  /* Should now be safe to put "sanitized" value into struct: */
+  math_opts->iopts[index] = val;
+}
+
+void MC_SetOp(const char* param, int val)
+{
+  MC_SetOpt(MC_MapTextToIndex(param), val);
+}
+
+int MC_GetOpt(unsigned int index)
+{
+  if (index >= NOPTS)
+  {
+    mcdprintf("Invalid option index: %d\n", index);
+    return MC_MATH_OPTS_INVALID;
+  }
+  if (!math_opts)
+  {
+    printf("Invalid options list!\n");
+    return MC_MATH_OPTS_INVALID;
+  }
+  return math_opts->iopts[index];
+}
+
+int MC_GetOp(const char* param)
+{
+  return MC_GetOpt(MC_MapTextToIndex(param) );
+}
+
+int MC_VerifyOptionListSane(void)
+{
+  return strcmp(MC_OPTION_TEXT[NOPTS], "END_OF_OPTS") == 0;
+}
+
+int MC_MaxFormulaSize(void)
+{
+  return max_formula_size;
+}
+
+int MC_MaxAnswerSize(void)
+{
+  return max_answer_size;
+}
+
+void MC_ResetFlashCard(MC_FlashCard* fc)
+{
+  if (!fc || !fc->formula_string || !fc->answer_string)
+    return;
+  strncpy(fc->formula_string, " ", max_formula_size);
+  strncpy(fc->answer_string, " ", max_answer_size);
+  fc->answer = 0;
+  fc->difficulty = 0;
+}
+
+int MC_FlashCardGood(const MC_FlashCard* fc)
+{
+  return fc && fc->formula_string && fc->answer_string;
+}
+
+int find_divisor(int a)
+{
+  int div = 1; //the divisor to return
+  int realisticpasses = 3; //reasonable time after which a minimum should be met
+  int i;
+  do
+    for (i = 0; i < NPRIMES; ++i) //test each prime
+      if (a % smallprimes[i] == 0)  //if it is a prime factor,
+        if (rand() % (i + 1) == 0) //maybe we'll keep it
+          if (div * smallprimes[i] <= MC_GetOpt(MAX_DIVISOR) ) //if we can,
+            div *= smallprimes[i]; //update our real divisor
+  //keep going if the divisor is too small
+  while (div < MC_GetOpt(MIN_DIVISOR) && --realisticpasses); 
+  
+  return div;
+}
+
+
+//Computes (approximately) the number of questions that will be returned
+//by add_all_valid() as specified by the current options. This does not 
+//take into account screening out of invalid questions, such
+//as divide-by-zero and questions like "0 x ? = 0".
+static int calc_num_valid_questions(void)
+{
+  int total_questions = 0;
+  int k = 0;
+  //First add the number of typing questions
+  if (MC_GetOpt(TYPING_PRACTICE_ALLOWED))
+    total_questions += (MC_GetOpt(MAX_TYPING_NUM) - MC_GetOpt(MIN_TYPING_NUM));
+
+  //Now add how many questions we will have for each operation:
+  for (k = MC_OPER_ADD; k < MC_NUM_OPERS; ++k)
+  {
+    int num_this_oper = 0;
+    int formats_this_oper = 0;
+
+    if (!MC_GetOpt(k + ADDITION_ALLOWED) )
+      continue;
+
+    //calculate number of ordered pairs of first and second operands:
+    //note the "+ 1" is due to the ranges being inclusive
+    num_this_oper = (MC_GetOpt(MAX_AUGEND + 4 * k) - MC_GetOpt(MIN_AUGEND + 4 * k) + 1)
+                    *
+                    (MC_GetOpt(MAX_ADDEND + 4 * k) - MC_GetOpt(MIN_ADDEND + 4 * k) + 1);
+    //check what formats are allowed
+    if (MC_GetOpt(FORMAT_ANSWER_LAST) && MC_GetOpt(FORMAT_ADD_ANSWER_LAST + k * 3))
+      formats_this_oper++;
+    if (MC_GetOpt(FORMAT_ANSWER_FIRST) && MC_GetOpt(FORMAT_ADD_ANSWER_FIRST + k * 3))
+      formats_this_oper++;
+    if (MC_GetOpt(FORMAT_ANSWER_MIDDLE) && MC_GetOpt(FORMAT_ADD_ANSWER_MIDDLE + k * 3))
+      formats_this_oper++;
+    //Get total of e.g. addition questions:
+    num_this_oper *= formats_this_oper;
+    //add to overall total:
+    total_questions += num_this_oper;
+  }
+
+  //TODO will also need to count up the COMPARISON questions once
+  //they are implemented
+  {
+  }
+
+  mcdprintf("calc_num_valid_questions():\t%d\n", total_questions);
+  return total_questions;
+}
+
+
+//NOTE end_of_list** needs to be doubly indirect because otherwise the end does not
+//get updated in the calling code
+//NOTE the difficulty is set as add = 1, sub = 2, mult = 3, div = 4, plus a 2 point
+//bonus if the format is a "missing number".
+MC_MathQuestion* add_all_valid(MC_ProblemType pt, MC_MathQuestion* list, MC_MathQuestion** end_of_list)
+{
+  int i, j;
+  int ans = 0, tmp;
+  MC_Operation k;
+  MC_MathQuestion* tnode;
+
+  mcdprintf("Entering add_all_valid(%d)\n", pt);
+  mcdprintf("List already has %d questions\n", list_length(list));
+
+  //make sure this problem type is actually allowed
+  if (!MC_GetOpt(pt + TYPING_PRACTICE_ALLOWED) )
+    return list;
+
+  //add all typing questions in range
+  if (pt == MC_PT_TYPING)
+  {
+    mcdprintf("Adding typing...\n");
+    for (i = MC_GetOpt(MIN_TYPING_NUM); i <= MC_GetOpt(MAX_TYPING_NUM); ++i)
+    {
+      mcdprintf("(%d)\n", i);
+      tnode = allocate_node();
+      if(!tnode)
+      {
+        fprintf(stderr, "In add_all_valid() - allocate_node() failed!\n");
+        delete_list(list);
+        return NULL;
+      }
+
+      snprintf(tnode->card.formula_string, max_formula_size, "%d", i);
+      snprintf(tnode->card.answer_string, max_formula_size, "%d", i);
+      tnode->card.difficulty = 1;
+      list = insert_node(list, *end_of_list, tnode);
+      *end_of_list = tnode;
+    }
+  }
+
+  //add all allowed arithmetic questions
+  else if (MC_PT_ARITHMETIC)
+  {
+    mcdprintf("Adding arithmetic...\n");
+
+    // The k loop iterates through the four arithmetic operations:
+    // k = 0 means addition
+    // k = 1 means subtraction
+    // k = 2 means multiplication
+    // k = 3 means division
+    for (k = MC_OPER_ADD; k < MC_NUM_OPERS; ++k)
+    {
+      if (!MC_GetOpt(k + ADDITION_ALLOWED) )
+        continue;
+      mcdprintf("\n*%d*\n", k);
+
+      // The i loop iterates through the first value in the question:
+      for (i = MC_GetOpt(MIN_AUGEND + 4 * k); i <= MC_GetOpt(MAX_AUGEND + 4 * k); ++i)
+      {
+        mcdprintf("\n%d:\n", i);
+
+        // The j loop iterates through the second value in the question:
+        for (j = MC_GetOpt(MIN_ADDEND + 4 * k); j <= MC_GetOpt(MAX_ADDEND + 4 * k); ++j)
+        {
+          // Generate the third number according to the operation.
+          // Although it is called "ans", it will not be the actual
+          // answer if it is a "missing number" type problem
+          // (e.g. "3 x ? = 12")
+          // We also filter out invalid questions here
+          switch (k)
+          {
+            case MC_OPER_ADD:
+            {
+              ans = i + j;
+              // throw anything over MAX_ANSWER
+              if (ans > MC_GetOpt(MAX_ANSWER))
+                continue;
+              break;
+            }
+            case MC_OPER_SUB:
+            {
+              ans = i - j;
+              // throw out negatives if they aren't allowed:
+              if (ans < 0 && !MC_GetOpt(ALLOW_NEGATIVES))
+                continue;
+              // throw anything over MAX_ANSWER
+              if (ans > MC_GetOpt(MAX_ANSWER))
+                continue;
+              break;
+            }
+            case MC_OPER_MULT:
+            {
+              ans = i * j;
+              // throw anything over MAX_ANSWER
+              if (ans > MC_GetOpt(MAX_ANSWER))
+                continue;
+              break;
+            }
+            case MC_OPER_DIV:
+            {
+               // throw anything over MAX_ANSWER
+              if (i * j > MC_GetOpt(MAX_ANSWER))
+                continue;
+
+              tmp = i;
+              i *= j;
+              ans = j;
+              j = tmp;
+              break;
+            }
+            default:
+              fprintf(stderr, "Unrecognized operation type: %d\n", k);
+              continue;
+          }
+
+          mcdprintf("Generating: %d %c %d = %d\n", i, operchars[k], j, ans);
+
+          //add each format, provided it's allowed in general and for this op
+
+          // Questions like "a + b = ?"
+          if (MC_GetOpt(FORMAT_ANSWER_LAST) && MC_GetOpt(FORMAT_ADD_ANSWER_LAST + k * 3))
+          {
+            // Avoid division by zero:
+            if (k == MC_OPER_DIV && j == 0)
+            {
+              // need to restore i and j to original values so loop works:
+              j = ans;
+              i = tmp;
+              continue;
+            }
+
+            tnode = allocate_node();
+            if(!tnode)
+            {
+              fprintf(stderr, "In add_all_valid() - allocate_node() failed!\n");
+              delete_list(list);
+              return NULL;
+            }
+
+            snprintf(tnode->card.answer_string, max_formula_size, "%d", ans);
+            snprintf(tnode->card.formula_string, max_formula_size,
+                     "%d %c %d = ?", i, operchars[k], j);
+            tnode->card.difficulty = k + 1;
+            list = insert_node(list, *end_of_list, tnode);
+            *end_of_list = tnode;
+          }
+
+
+          // Questions like "? + b = c"
+          if (MC_GetOpt(FORMAT_ANSWER_FIRST) && MC_GetOpt(FORMAT_ADD_ANSWER_FIRST + k * 3) )
+          {
+            // Avoid questions with indeterminate answer:
+            // e.g. "? x 0 = 0"
+            if (k == MC_OPER_MULT && j == 0)
+            {
+              continue;
+            }
+            // Avoid division by zero:
+            if (k == MC_OPER_DIV && j == 0)
+            {
+              // need to restore i and j to original values so loop works:
+              j = ans;
+              i = tmp;
+              continue;
+            }
+
+            tnode = allocate_node();
+            if(!tnode)
+            {
+              fprintf(stderr, "In add_all_valid() - allocate_node() failed!\n");
+              delete_list(list);
+              return NULL;
+            }
+
+            snprintf(tnode->card.answer_string, max_formula_size, "%d", i);
+            snprintf(tnode->card.formula_string, max_formula_size,
+                     "? %c %d = %d", operchars[k], j, ans);
+            tnode->card.difficulty = k + 3;
+            list = insert_node(list, *end_of_list, tnode);
+            *end_of_list = tnode;
+          }
+
+
+          // Questions like "a + ? = c"
+          if (MC_GetOpt(FORMAT_ANSWER_MIDDLE) && MC_GetOpt(FORMAT_ADD_ANSWER_MIDDLE + k * 3))
+          {
+            // Avoid questions with indeterminate answer:
+            // e.g. "0 x ? = 0"
+            if (k == MC_OPER_MULT && i == 0)
+              continue;
+
+            // e.g. "0 / ? = 0"
+            if (k == MC_OPER_DIV && i == 0)
+            {
+              // need to restore i and j to original values so loop works:
+              j = ans;
+              i = tmp;
+              continue;
+            }
+
+            tnode = allocate_node();
+            if(!tnode)
+            {
+              fprintf(stderr, "In add_all_valid() - allocate_node() failed!\n");
+              delete_list(list);
+              return NULL;
+            }
+
+            snprintf(tnode->card.answer_string, max_formula_size, "%d", j);
+            snprintf(tnode->card.formula_string, max_formula_size,
+                     "%d %c ? = %d", i, operchars[k], ans);
+            tnode->card.difficulty = k + 3;
+            list = insert_node(list, *end_of_list, tnode);
+            *end_of_list = tnode;
+          }
+          //If we divided, reset i and j so loop works correctly
+          if (k == MC_OPER_DIV)
+          {
+            j = ans;
+            i = tmp;
+            mcdprintf("resetting to %d %d\n", j, i);
+          }
+        }
+      }
+    }
+  }
+  //add all comparison questions (TODO implement them!)
+  else if (pt == MC_PT_COMPARISON)
+  {
+    for (i = MC_GetOpt(MIN_COMPARATOR); i < MC_GetOpt(MAX_COMPARATOR); ++i)
+    {
+      for (j = MC_GetOpt(MIN_COMPARISAND); j < MC_GetOpt(MAX_COMPARISAND); ++j)
+      {
+        tnode = allocate_node();
+        if(!tnode)
+        {
+          fprintf(stderr, "In add_all_valid() - allocate_node() failed!\n");
+          delete_list(list);
+          return NULL;
+        }
+
+        snprintf(tnode->card.formula_string, max_formula_size, "%d ? %d", i,j);
+        snprintf(tnode->card.answer_string, max_formula_size,
+                 i < j ? "<" : 
+                 i > j ? ">" : 
+                         "=");
+        tnode->card.difficulty = 1;
+        list = insert_node(list, *end_of_list, tnode);
+        *end_of_list = tnode;
+      }
+    }
+  }
+  mcdprintf("Exiting add_all_valid()\n");  
+  mcdprintf("List now has %d questions\n\n", list_length(list));
+
+  return list;
+}
+
+MC_MathQuestion* find_node(MC_MathQuestion* list, int num)
+{
+  while (--num > 0 && list)
+    list = list->next;
+  return list;
+}
+
+void reformat_arithmetic(MC_FlashCard* card, MC_Format f)
+{
+  int i, j;
+  char* beg = 0;
+  char* end = 0;
+  char nans[max_answer_size];
+  char nformula[max_formula_size + max_answer_size]; //gets a bit larger than usual in the meantime
+  
+  {
+    //snprintf(nans, max_answer_size, "%s", card->answer_string);
+   
+    //insert old answer where question mark was
+    for (i = 0, j = 0; card->formula_string[j] != '?'; ++i, ++j)
+      nformula[i] = card->formula_string[j];
+    i += snprintf(nformula + i, max_answer_size - 1, "%s", card->answer_string);
+    snprintf(nformula + i, max_formula_size - i, "%s", card->formula_string + j + 1);
+
+    //replace the new answer with a question mark
+    if (f == MC_FORMAT_ANS_LAST)
+      beg = strrchr(nformula, ' ') + 1;
+    if (f == MC_FORMAT_ANS_FIRST)
+      beg = nformula;
+    if (f == MC_FORMAT_ANS_MIDDLE)
+      beg = strchr(nformula, ' ') + 3;
+    end = strchr(beg + 1, ' ');
+    if (!end)
+      end = "";
+    //we now have beg = first digit of number to replace, end = the char after
+    sscanf(beg, "%s", nans);
+    *beg = 0; //sequester the first half of the string
+    snprintf(card->formula_string, max_formula_size, "%s?%s", nformula, end);
+    snprintf(card->answer_string, max_answer_size, nans);
+    card->answer = atoi(card->answer_string);
+  }
+}

Added: tuxmath/trunk/src/mathcards.h
===================================================================
--- tuxmath/trunk/src/mathcards.h	                        (rev 0)
+++ tuxmath/trunk/src/mathcards.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,296 @@
+/*
+
+        mathcards.h
+
+        Description: contains headers for a flashcard-type math game.
+        This is a sort of interface-independent backend that could be used with a different
+        user interface. Developed as an enhancement to Bill Kendrick's "Tux of Math Command"
+        (aka tuxmath).  If tuxmath were a C++ program, this would be a C++ class.
+
+        Author: David Bruce <dbruce at tampabay.rr.com>, (C) 2006
+
+        Copyright: See COPYING file that comes with this distribution (briefly, GNU GPL version 2 or later)
+
+*/
+#ifndef MATHCARDS_H
+#define MATHCARDS_H
+
+#define MC_DEBUG
+#ifdef MC_DEBUG
+#define mcdprintf(...) printf(__VA_ARGS__)
+#else
+#define mcdprintf(...) 0
+#endif
+
+#define MC_USE_NEWARC
+
+/* different classes of problems TuxMath will ask */
+typedef enum _MC_ProblemType {
+  MC_PT_TYPING,
+  MC_PT_ARITHMETIC,
+  MC_PT_COMPARISON,
+  MC_NUM_PTYPES
+} MC_ProblemType;
+
+/* type of math operation used in an arithmetic question */
+typedef enum _MC_Operation {
+  MC_OPER_ADD,
+  MC_OPER_SUB,
+  MC_OPER_MULT,
+  MC_OPER_DIV,
+  MC_NUM_OPERS
+} MC_Operation;
+
+/* math question formats: */
+typedef enum _MC_Format {
+  MC_FORMAT_ANS_LAST,     /* a + b = ? */
+  MC_FORMAT_ANS_FIRST,    /* ? + b = c */
+  MC_FORMAT_ANS_MIDDLE,    /* a + ? = c */
+  MC_NUM_FORMATS
+} MC_Format;
+
+
+/*
+Indices for the various integer options. These are NOT the actual values!
+Actual values are accessed as such: options.iopts[PLAY_THROUGH_LIST] = val;
+Creating additional [integral] options is now centralized--it should only
+be necessary to add to this list, the list of text, and the list of
+defaults. (Besides actually using the new options!)
+*/
+enum {
+  NOT_VALID_OPTION = -1     ,
+  PLAY_THROUGH_LIST = 0     , /* play until all questions answered correctly */
+  QUESTION_COPIES           , /* # times each question is put in list */
+  REPEAT_WRONGS             , /* reuse incorrectly answered questions or not */
+  COPIES_REPEATED_WRONGS    , /* how many copies of an incorrectly answered question to re-insert*/
+  ALLOW_NEGATIVES           ,
+  MAX_ANSWER                ,
+  MAX_QUESTIONS             ,
+  MAX_FORMULA_NUMS          ,
+  MIN_FORMULA_NUMS          ,
+
+  //NOTE: Do _not_ rearrange the FORMAT values because the functions
+  //rely on index arithmetic to iterate through these, and will be
+  //broken if the relative position changes!
+  FORMAT_ANSWER_LAST        , /* question format is: a + b = ? */
+  FORMAT_ANSWER_FIRST       , /* question format is: ? + b = c */
+  FORMAT_ANSWER_MIDDLE      , /* question format is: a + ? = c */
+  FORMAT_ADD_ANSWER_LAST    , /* a + b = ?    */
+  FORMAT_ADD_ANSWER_FIRST   , /* ? + b = c    */
+  FORMAT_ADD_ANSWER_MIDDLE  , /* a + ? = c    */
+  FORMAT_SUB_ANSWER_LAST    , /* a - b = ?    */
+  FORMAT_SUB_ANSWER_FIRST   , /* ? - b = c    */
+  FORMAT_SUB_ANSWER_MIDDLE  , /* a - ? = c    */
+  FORMAT_MULT_ANSWER_LAST   , /* a * b = ?    */
+  FORMAT_MULT_ANSWER_FIRST  , /* ? * b = c    */
+  FORMAT_MULT_ANSWER_MIDDLE , /* a * ? = c    */
+  FORMAT_DIV_ANSWER_LAST    , /* a / b = ?    */
+  FORMAT_DIV_ANSWER_FIRST   , /* ? / b = c    */
+  FORMAT_DIV_ANSWER_MIDDLE  , /* a / ? = c    */
+
+  ADDITION_ALLOWED          ,
+  SUBTRACTION_ALLOWED       ,
+  MULTIPLICATION_ALLOWED    ,
+  DIVISION_ALLOWED          ,
+  TYPING_PRACTICE_ALLOWED   ,
+  ARITHMETIC_ALLOWED        ,
+  COMPARISON_ALLOWED        ,
+
+  MIN_AUGEND                , /* augend + addend = sum */
+  MAX_AUGEND                ,
+  MIN_ADDEND                ,
+  MAX_ADDEND                ,
+
+  MIN_MINUEND               , /* minuend - subtrahend = difference */
+  MAX_MINUEND               ,
+  MIN_SUBTRAHEND            ,
+  MAX_SUBTRAHEND            ,
+
+  MIN_MULTIPLIER            , /* multiplier * multiplicand = product */
+  MAX_MULTIPLIER            ,
+  MIN_MULTIPLICAND          ,
+  MAX_MULTIPLICAND          ,
+
+  MIN_DIVISOR               , /* dividend/divisor = quotient */
+  MAX_DIVISOR               , /* note - generate_list() will prevent */
+  MIN_QUOTIENT              , /* questions with division by zero.    */
+  MAX_QUOTIENT              ,
+
+  MIN_TYPING_NUM            , /* range for "typing tutor" mode, for  */
+  MAX_TYPING_NUM            , /* kids just learning to use keyboard. */
+
+  MIN_COMPARATOR            , /* left comparison operand */
+  MAX_COMPARATOR            ,
+  MIN_COMPARISAND           , /* right comparison operannd */
+  MAX_COMPARISAND           ,
+
+  RANDOMIZE                 , /* whether to shuffle cards */
+
+  COMPREHENSIVE             , /* whether to generate all questions 'in order' */
+  AVG_LIST_LENGTH           , /* the average number of questions in a list */
+  VARY_LIST_LENGTH          , /* whether to randomly adjust list length */
+
+  NOPTS
+};
+
+extern const char* const MC_OPTION_TEXT[];
+extern const int MC_DEFAULTS[];
+extern const char operchars[MC_NUM_OPERS];
+
+/* default values for math_options */
+#define MC_MAX_DIGITS 3 
+#define MC_GLOBAL_MAX 999          /* This is the largest absolute value that */
+                                   /* can be entered for math question values.*/
+#define MC_MATH_OPTS_INVALID -9999 /* Return value for accessor functions     */
+                                   /* if math_opts not valid                  */
+//#define DEFAULT_FRACTION_TO_KEEP 1
+
+
+typedef struct _MC_Options
+{
+  int iopts[NOPTS];
+} MC_Options;
+
+#ifndef MC_USE_NEWARC
+/* struct for individual "flashcard" */
+typedef struct MC_FlashCard {
+  int num1;
+  int num2;
+  int num3;
+  int operation;
+  int format;
+  char formula_string[MC_FORMULA_LEN];
+  char answer_string[MC_ANSWER_LEN];
+} MC_FlashCard;
+#else
+/* experimental struct for a more generalized flashcard */
+typedef struct _MC_FlashCard {
+  char* formula_string;
+  char* answer_string;
+  int answer;
+  int difficulty;
+} MC_FlashCard;
+#endif
+
+/* struct for node in math "flashcard" list */
+typedef struct MC_MathQuestion {
+  MC_FlashCard card;
+  struct MC_MathQuestion* next;
+  struct MC_MathQuestion* previous;
+  int randomizer;
+} MC_MathQuestion;
+
+
+/* "public" function prototypes: these functions are how */
+/* a user interface communicates with MathCards:         */
+/* TODO provide comments thoroughly explaining these functions */
+
+/*  MC_Initialize() sets up the struct containing all of  */
+/*  settings regarding math questions.  It should be      */
+/*  called before any other function.  Many of the other  */
+/*  functions will not work properly if MC_Initialize()   */
+/*  has not been called. It only needs to be called once, */
+/*  i.e when the program is starting, not at the beginning*/
+/*  of each math game for the player. Returns 1 if        */
+/*  successful, 0 otherwise.                              */
+int MC_Initialize(void);
+
+/*  MC_StartGame() generates the list of math questions   */
+/*  based on existing settings. It should be called at    */
+/*  the beginning of each math game for the player.       */
+/*  Returns 1 if resultant list contains 1 or more        */
+/*  questions, 0 if list empty or not generated           */
+/*  successfully.                                         */
+int MC_StartGame(void);
+
+/*  MC_StartGameUsingWrongs() is like MC_StartGame(),     */
+/*  but uses the incorrectly answered questions from the  */
+/*  previous game for the question list as a review form  */
+/*  of learning. If there were no wrong answers (or no    */
+/*  previous game), it behaves just like MC_StartGame().  */
+/*  FIXME wonder if it should generate a message if the   */
+/*  list is created from settings because there is no     */
+/*  valid wrong question list?                            */
+int MC_StartGameUsingWrongs(void);
+
+/*  MC_NextQuestion() takes a pointer to an allocated     */
+/*  MC_MathQuestion struct and fills in the fields for    */
+/*  use by the user interface program. It basically is    */
+/*  like taking the next flashcard from the pile.         */
+/*  Returns 1 if question found, 0 if list empty/invalid  */
+/*  or if argument pointer is invalid                     */
+int MC_NextQuestion(MC_FlashCard* q);
+
+/*  MC_AnsweredCorrectly() is how the user interface      */
+/*  tells MathCards that the question has been answered   */
+/*  correctly. Returns 1 if no errors.                    */
+int MC_AnsweredCorrectly(MC_FlashCard* q);
+
+/*  MC_NotAnsweredCorrectly() is how the user interface    */
+/*  tells MathCards that the question has not been        */
+/*  answered correctly. Returns 1 if no errors.           */
+int MC_NotAnsweredCorrectly(MC_FlashCard* q);
+
+/*  Like MC_NextQuestion(), but takes "flashcard" from    */
+/*  pile of incorrectly answered questions.               */
+/*  Returns 1 if question found, 0 if list empty/invalid  */
+int MC_NextWrongQuest(MC_FlashCard* q);
+
+/*  Returns 1 if all have been answered correctly,        */
+/* 0 otherwise.                                           */
+int MC_MissionAccomplished(void);
+
+/*  Returns number of questions left (either in list      */
+/*  or "in play")                                         */
+int MC_TotalQuestionsLeft(void);
+
+/*  Returns questions left in list, NOT                   */
+/*  including questions currently "in play".              */
+int MC_ListQuestionsLeft(void);
+
+/*  To keep track of how long students take to answer the */
+/*  questions, one can report the time needed to answer   */
+/*  an individual question:                               */
+int MC_AddTimeToList(float t);
+/*  Note that initialization of the list is handled by    */
+/*  MC_StartGame.                                         */
+
+/*  Tells MathCards to clean up - should be called when   */
+/*  user interface program exits.                         */
+void MC_EndGame(void);
+
+/*  Prints contents of math_opts struct in human-readable   */
+/*  form to given file. "verbose" tells the function to     */
+/*  write a lot of descriptive "help"-type info for each    */
+/*  option (intended to make config files self-documenting).*/
+void MC_PrintMathOptions(FILE* fp, int verbose);
+
+/* Additional functions used to generate game summary files: */
+int MC_PrintQuestionList(FILE* fp);
+int MC_PrintWrongList(FILE* fp);
+int MC_StartingListLength(void);
+int MC_WrongListLength(void);
+int MC_NumAnsweredCorrectly(void);
+int MC_NumNotAnsweredCorrectly(void);
+float MC_MedianTimePerQuestion(void);
+
+/********************************************
+Public functions for new mathcards architecture
+*********************************************/
+/* Return the array index of the given text, e.g. randomize->47 */
+unsigned int MC_MapTextToIndex(const char* text);
+void MC_SetOpt(unsigned int index, int val); //access directly,for internal use
+int MC_GetOpt(unsigned int index);
+void MC_SetOp(const char* param, int val); //access by text, for config reading
+int MC_GetOp(const char* param);
+int MC_VerifyOptionListSane(void);
+int MC_MaxFormulaSize(void); //amount of memory needed to safely hold strings
+int MC_MaxAnswerSize(void);
+MC_FlashCard MC_AllocateFlashcard();
+void MC_FreeFlashcard(MC_FlashCard* fc);
+void MC_ResetFlashCard(MC_FlashCard* fc); //empty flashcard of strings & values
+int MC_FlashCardGood(const MC_FlashCard* fc); //verifies a flashcard is valid
+/* Reorganize formula_string and answer_string to render the same equation
+   in a different format */
+void reformat_arithmetic(MC_FlashCard* card, MC_Format f);
+#endif

Added: tuxmath/trunk/src/multiplayer.c
===================================================================
--- tuxmath/trunk/src/multiplayer.c	                        (rev 0)
+++ tuxmath/trunk/src/multiplayer.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,275 @@
+/*
+
+multiplayer.h - Provides routines for organizing and running a turn-based
+                multiplayer that can accommodate up to four players (more with
+                a recompilation)
+
+Author: B. Luchen
+
+*/
+
+#include "tuxmath.h"
+#include "SDL_extras.h"
+#include "multiplayer.h"
+#include "game.h"
+#include "options.h"
+#include "fileops.h"
+#include "highscore.h"
+#include "credits.h"
+
+int params[NUM_PARAMS] = {0, 0, 0, 0};
+
+int inprogress = 0;
+int pscores[MAX_PLAYERS];
+char* pnames[MAX_PLAYERS];
+
+//local function decs
+static void showWinners(int* order, int num); //show a sequence recognizing winner
+static int initMP();
+static void cleanupMP();
+
+void mp_set_parameter(unsigned int param, int value)
+{
+  if (inprogress)
+  {
+    tmdprintf("Oops, tried to set param %d in the middle of a game\n", param);
+    return;
+  }
+  params[param] = value;
+}
+
+void mp_run_multiplayer()
+{
+  int i;
+  int round = 1;
+  int currentplayer = 0;
+  int result = 0;
+  int done = 0;
+  int activeplayers = params[PLAYERS];
+  int winners[MAX_PLAYERS];
+
+  for (i = 0; i < MAX_PLAYERS; ++i)
+    winners[i] = -1;
+
+  if (initMP() )
+  {
+    printf("Initialization failed, bailing out\n");
+    return;
+  }
+
+  //cycle through players until all but one has lost
+  if (params[MODE] == ELIMINATION) 
+  {
+    while(!done)
+    {
+      //TODO maybe gradually increase difficulty
+      game_set_start_message(pnames[currentplayer], "Go!", "", "");
+      result = game();
+
+      if (result == GAME_OVER_LOST || result == GAME_OVER_ESCAPE)
+      {
+        //eliminate player
+        pscores[currentplayer] = 0xbeef;
+        winners[--activeplayers] = currentplayer;
+      }
+            
+      do //move to the next player
+      {
+        ++currentplayer;
+        currentplayer %= params[PLAYERS];
+        if (currentplayer == 0)
+          ++round;
+      } 
+      while (pscores[currentplayer] == 0xbeef); //skip over eliminated players
+      
+      if (activeplayers <= 1) //last man standing!
+      {
+        tmdprintf("%d wins\n", currentplayer);
+        winners[0] = currentplayer;
+        done = 1;
+      }
+    }
+  }
+  //players take turns, accumulating score, and the highest score wins
+  else if (params[MODE] == SCORE_SWEEP)
+  {
+    int hiscore = 0;
+    int currentwinner = -1;
+
+    //play through rounds
+    for (round = 1; round <= params[ROUNDS]; ++round)
+    {
+      for (currentplayer = 0; currentplayer < params[PLAYERS]; ++currentplayer)
+      {
+        game_set_start_message(pnames[currentplayer], "Go!", NULL, NULL);
+        result = game();
+        pscores[currentplayer] += Opts_LastScore(); //add this player's score
+        if (result == GAME_OVER_WON)
+          pscores[currentplayer] += 500; //plus a possible bonus
+      }
+    }
+    
+    //sort out winners
+    for (i = 0; i < params[PLAYERS]; ++i)
+    {
+      for (currentplayer = 0; currentplayer < params[PLAYERS]; ++currentplayer)
+      {
+        if (pscores[currentplayer] > hiscore)
+        {
+          hiscore = pscores[currentplayer];
+          currentwinner = currentplayer;
+        }
+      winners[i] = currentwinner;
+      pscores[currentwinner] = 0;
+      }
+    }
+  }
+  
+  tmdprintf("Game over; showing winners\n");
+  showWinners(winners, params[PLAYERS]);
+  cleanupMP();
+}
+
+int mp_get_player_score(int playernum)
+{
+  if (playernum > params[PLAYERS])
+  {
+    tmdprintf("No player %d!\n", playernum);
+    return 0;
+  }
+  return pscores[playernum];
+}
+
+const char* mp_get_player_name(int playernum)
+{
+  if (playernum > params[PLAYERS])
+  {
+    tmdprintf("No player %d!\n", playernum);
+    return 0;
+  }
+  return pnames[playernum];
+}
+
+int mp_get_parameter(unsigned int param)
+{
+  if (param > NUM_PARAMS)
+  {
+    printf("Invalid mp_param index: %d\n", param);
+    return 0;
+  }
+  return params[param];
+}
+
+//TODO a nicer-looking sequence that also recognizes second place etc.
+void showWinners(int* winners, int num)
+{
+  int skip = 0;
+  const int boxspeed = 3;
+  char text[HIGH_SCORE_NAME_LENGTH + strlen(" wins!")];
+  SDL_Rect box = {screen->w / 2, screen->h / 2, 0, 0};
+  SDL_Rect center = box;
+  SDL_Event evt;
+
+//  const char* winnername = (winners[0] == -1 ? "Nobody" : pnames[winners[0]] );
+  
+  tmdprintf(pnames[winners[0]] );
+  tmdprintf("%d\n", snprintf(text, HIGH_SCORE_NAME_LENGTH + strlen(" wins!"),
+                    "%s wins!", winnername) );
+  tmdprintf("Win text: %s\n", text);
+
+  DarkenScreen(1);
+
+  while (box.h < screen->h || box.w < screen->w)
+  {
+    //expand black box
+    box.x -= boxspeed;
+    box.y -= boxspeed;
+    box.h += boxspeed * 2;
+    box.w += boxspeed * 2;
+
+    //reveal text specifying the winner
+    SDL_FillRect(screen, &box, 0);
+    draw_text(text, center);
+    SDL_UpdateRect(screen, box.x, box.y, box.w, box.h);
+
+    while (SDL_PollEvent(&evt) )
+      if (evt.type == SDL_KEYDOWN && evt.key.keysym.sym == SDLK_ESCAPE)
+        skip = 1;
+    if (skip)
+      break;
+    SDL_Delay(50);
+  }
+  //in case we've skipped, cover the whole screen
+  SDL_FillRect(screen, NULL, 0);
+  draw_text(text, center);
+  SDL_Flip(screen);
+  WaitForEvent(SDL_KEYDOWNMASK | SDL_MOUSEBUTTONDOWNMASK);
+}
+
+int initMP()
+{
+  int i;
+  char nrstr[HIGH_SCORE_NAME_LENGTH * 3];
+  int nplayers = params[PLAYERS];
+
+  const char* config_files[5] = {
+    "multiplay/space_cadet",
+    "multiplay/scout",
+    "multiplay/ranger",
+    "multiplay/ace",
+    "multiplay/commando"
+  };
+
+  tmdprintf("Reading in difficulty settings...\n");
+  if (!read_global_config_file() ||
+      !read_named_config_file("multiplay/mpoptions") ||
+      !read_named_config_file(config_files[params[DIFFICULTY]]) )
+  {
+    printf("Couldn't read in settings for %s\n",
+           config_files[params[DIFFICULTY]] );
+    return 1;
+  }
+
+  pscores[0] = pscores[1] = pscores[2] = pscores[3] = 0;
+  pnames[0] = pnames[1] = pnames[2] = pnames[3] = NULL;
+
+  //allocate and enter player names
+  for (i = 0; i < nplayers; ++i)
+    pnames[i] = malloc((1 + 3 * HIGH_SCORE_NAME_LENGTH) * sizeof(char) );
+  for (i = 0; i < nplayers; ++i)
+  {
+    if (pnames[i])
+      NameEntry(pnames[i], "Who is playing?", "Enter your name:");
+    else
+    {
+      printf("Can't allocate name %d!\n", i);
+      return 1;
+    }
+  }
+  
+  //enter how many rounds
+  if (params[MODE] == SCORE_SWEEP)
+  {
+    while (params[ROUNDS] <= 0)
+    {
+      NameEntry(nrstr, "How many rounds will you play?", "Enter a number");
+      params[ROUNDS] = atoi(nrstr);
+    }
+  }
+  inprogress = 1; //now we can start the game
+  return 0;
+}
+
+void cleanupMP()
+{
+  int i;
+
+  for (i = 0; i < params[PLAYERS]; ++i)
+    if (pnames[i])
+      free(pnames[i]);
+      
+  for (i = 0; i < NUM_PARAMS; ++i)
+    params[i] = 0;
+    
+  inprogress = 0;
+}

Added: tuxmath/trunk/src/multiplayer.h
===================================================================
--- tuxmath/trunk/src/multiplayer.h	                        (rev 0)
+++ tuxmath/trunk/src/multiplayer.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,36 @@
+#ifndef MULTIPLAYER_H
+#define MULTIPLAYER_H
+
+/*
+
+multiplayer.h - Provides routines for organizing and running a turn-based
+                multiplayer that can accommodate up to four players (more with
+                a recompilation)
+
+Author: B. Luchen
+
+*/
+
+#define MAX_PLAYERS 4
+
+enum {
+  PLAYERS,
+  ROUNDS,
+  DIFFICULTY,
+  MODE,
+  NUM_PARAMS
+};
+
+typedef enum {
+  SCORE_SWEEP,
+  ELIMINATION
+} MP_Mode;
+
+void mp_set_parameter(unsigned int param, int value);
+int mp_get_parameter(unsigned int param);
+void mp_run_multiplayer();
+int mp_get_player_score(int playernum);
+const char* mp_get_player_name(int playernum);
+int mp_num_players();
+
+#endif // MULTIPLAYER_H

Added: tuxmath/trunk/src/options.c
===================================================================
--- tuxmath/trunk/src/options.c	                        (rev 0)
+++ tuxmath/trunk/src/options.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,1131 @@
+/*
+  options.c
+
+  For TuxMath
+  The options screen loop.
+
+  by Bill Kendrick
+  bill at newbreedsoftware.com
+  http://www.newbreedsoftware.com/
+
+  Modified extensively by David Bruce
+  dbruce at tampabay.rr.com
+
+  Part of "Tux4Kids" Project
+  http://www.tux4kids.com
+      
+  August 26, 2001 - July 11, 2007
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+//#include "SDL.h"
+
+#include "mathcards.h"
+
+#include "options.h"
+#include "fileops.h"
+#include "setup.h"
+#include "game.h"
+//#include "tuxmath.h"
+
+/* FIXME figure out what oper_override is supposed to do and make sure */
+/* this file behaves accordingly! */
+
+//int opers[NUM_OPERS], range_enabled[NUM_Q_RANGES];
+
+/* extern'd constants */
+
+const char* const OPTION_TEXT[NUM_GLOBAL_OPTS+1] = {
+  "PER_USER_CONFIG",
+  "USE_SOUND",
+  "MENU_SOUND",
+  "MENU_MUSIC",
+  "FULLSCREEN",
+  "USE_KEYPAD",
+  "USE_IGLOOS",
+  "END_OF_OPTS"
+};
+
+const int DEFAULT_GLOBAL_OPTS[NUM_GLOBAL_OPTS] = {
+  1,
+  1,
+  1,
+  1,
+  1,
+  0,
+  1
+};
+  
+
+/* file scope only now that accessor functions used: */
+static game_option_type* game_options;
+static global_option_type* global_options;
+
+/*local function prototypes: */
+static int int_to_bool(int i);
+//static int find_and_set_option(const char* name, int val);
+
+
+/********************************************************************/
+/*  "Public Methods" for options struct:                            */
+/********************************************************************/
+
+int Opts_Initialize(void)
+{
+  int i;
+  
+  game_options = malloc(sizeof(game_option_type));
+  global_options = malloc(sizeof(global_option_type));
+  /* bail out if no struct */
+  if (!game_options)
+    return 0;
+
+  /* set global program options */
+  for (i = 0; i < NUM_GLOBAL_OPTS; ++i)
+    global_options->iopts[i] = DEFAULT_GLOBAL_OPTS[i];
+    
+  /* set general game options */
+  global_options->iopts[PER_USER_CONFIG] = DEFAULT_PER_USER_CONFIG;
+  global_options->iopts[USE_SOUND] = DEFAULT_USE_SOUND;
+  global_options->iopts[MENU_SOUND] = DEFAULT_MENU_SOUND;
+  global_options->iopts[MENU_MUSIC] = DEFAULT_MENU_MUSIC;
+  global_options->iopts[FULLSCREEN] = DEFAULT_FULLSCREEN;
+  global_options->iopts[USE_KEYPAD] = DEFAULT_USE_KEYPAD;
+  global_options->iopts[USE_IGLOOS] = DEFAULT_USE_IGLOOS;
+  game_options->use_bkgd = DEFAULT_USE_BKGD;
+  game_options->help_mode = DEFAULT_HELP_MODE;
+  game_options->demo_mode = DEFAULT_DEMO_MODE;
+  game_options->oper_override = DEFAULT_OPER_OVERRIDE;
+  game_options->allow_pause = DEFAULT_ALLOW_PAUSE;
+  game_options->bonus_comet_interval = DEFAULT_BONUS_COMET_INTERVAL;
+  game_options->bonus_speed_ratio = DEFAULT_BONUS_SPEED_RATIO;
+  game_options->speed = DEFAULT_SPEED;
+  game_options->allow_speedup = DEFAULT_ALLOW_SPEEDUP;
+  game_options->speedup_factor = DEFAULT_SPEEDUP_FACTOR;
+  game_options->max_speed = DEFAULT_MAX_SPEED;
+  game_options->slow_after_wrong = DEFAULT_SLOW_AFTER_WRONG;
+  game_options->starting_comets = DEFAULT_STARTING_COMETS;
+  game_options->extra_comets_per_wave = DEFAULT_EXTRA_COMETS_PER_WAVE;
+  game_options->max_comets = DEFAULT_MAX_COMETS;
+  game_options->save_summary = DEFAULT_SAVE_SUMMARY;
+  game_options->sound_hw_available = DEFAULT_SOUND_HW_AVAILABLE;
+  game_options->use_feedback = DEFAULT_USE_FEEDBACK;
+  game_options->danger_level = DEFAULT_DANGER_LEVEL;
+  game_options->danger_level_speedup = DEFAULT_DANGER_LEVEL_SPEEDUP;
+  game_options->danger_level_max = DEFAULT_DANGER_LEVEL_MAX;
+  game_options->city_expl_handicap = DEFAULT_CITY_EXPL_HANDICAP;
+  game_options->last_score = DEFAULT_LAST_SCORE;
+
+  game_options->num_cities = DEFAULT_NUM_CITIES;   /* MUST BE AN EVEN NUMBER! */
+  game_options->max_city_colors = DEFAULT_MAX_CITY_COLORS;
+
+  #ifdef TUXMATH_DEBUG
+  print_game_options(stdout, 0);
+  #endif
+
+  return 1;
+}
+
+
+void Opts_Cleanup(void)
+{
+  if (game_options)
+  {
+    free(game_options);
+    game_options = 0;
+  }
+}
+
+
+//* "Set" functions for tuxmath options struct: */
+unsigned int Opts_MapTextToIndex(const char* text)
+{
+  int i;
+  for (i = 0; i < NUM_GLOBAL_OPTS; ++i)
+  {
+    if (0 == strcasecmp(text, OPTION_TEXT[i]) )
+      return i;
+  }
+  tmdprintf("'%s' isn't a global option\n", text);
+  return -1;
+}
+int Opts_GetGlobalOp(const char* text)
+{
+  int index = Opts_MapTextToIndex(text);
+  if (index < NUM_GLOBAL_OPTS)
+    return Opts_GetGlobalOpt(index);
+  return 0;
+}
+int Opts_GetGlobalOpt(unsigned int index)
+{
+  if (index < NUM_GLOBAL_OPTS)
+    return global_options->iopts[index];
+    
+  tmdprintf("Invalid global option index: %d\n", index);
+  return 0;
+}
+
+void Opts_SetGlobalOp(const char* text, int val)
+{
+  int index = Opts_MapTextToIndex(text);
+  if (index < NUM_GLOBAL_OPTS)
+    Opts_SetGlobalOpt(index, val);
+}
+    
+void Opts_SetGlobalOpt(unsigned int index, int val)
+{
+  if (index < NUM_GLOBAL_OPTS)
+    global_options->iopts[index] = val;
+  else
+    tmdprintf("Invalid global option index: %d\n", index);
+}
+  
+
+//void Opts_SetPerUserConfig(int val)
+//{
+//  global_options->iopts[PER_USER_CONFIG] = int_to_bool(val);
+//}
+//
+//
+//void Opts_SetUseSound(int val)
+//{
+//  if (val == -1)
+//    global_options->iopts[USE_SOUND] = val;
+//  else if (global_options->iopts[USE_SOUND] != -1)
+//    global_options->iopts[USE_SOUND] = int_to_bool(val);
+//}
+//
+//
+//void Opts_SetMenuSound(int val)
+//{
+//  global_options->iopts[MENU_SOUND] = int_to_bool(val);
+//}
+//
+//
+//void Opts_SetMenuMusic(int val)
+//{
+//  global_options->iopts[MENU_MUSIC] = int_to_bool(val);
+//}
+
+
+///* FIXME need to actually change screen resolution when this is called */
+//void Opts_SetFullscreen(int val)
+//{
+//  global_options->iopts[FULLSCREEN] = int_to_bool(val);
+//}
+
+
+void Opts_SetUseBkgd(int val)
+{
+  game_options->use_bkgd = int_to_bool(val);
+}
+
+
+void Opts_SetHelpMode(int val)
+{
+  game_options->help_mode = int_to_bool(val);
+}
+
+
+void Opts_SetDemoMode(int val)
+{
+  game_options->demo_mode = int_to_bool(val);
+}
+
+
+void Opts_SetOperOverride(int val)
+{
+  game_options->oper_override = int_to_bool(val);
+}
+
+
+//void Opts_SetUseKeypad(int val)
+//{
+//  global_options->iopts[USE_KEYPAD] = int_to_bool(val);
+//}
+
+
+void Opts_SetAllowPause(int val)
+{
+  game_options->allow_pause = int_to_bool(val);
+}
+
+
+//void Opts_SetUseIgloos(int val)
+//{
+//  global_options->iopts[USE_IGLOOS] = int_to_bool(val);
+//}
+
+
+void Opts_SetBonusCometInterval(int val)
+{
+  if (val < 0)
+    val = 0;
+  game_options->bonus_comet_interval = val;
+}
+
+
+void Opts_SetBonusSpeedRatio(float val)
+{
+  if (val < 1)
+  {
+    val = 1;
+    fprintf(stderr,"bonus_speed_ratio must be at least 1, resetting accordingly.\n");
+  }
+  if (val > MAX_BONUS_SPEED_RATIO)
+  {
+    val = MAX_BONUS_SPEED_RATIO;
+    fprintf(stderr,"Warning: requested bonus_speed_ratio above maximum, setting to %g.\n",MAX_BONUS_SPEED_RATIO);
+  }
+  game_options->bonus_speed_ratio = val;
+}
+
+
+void Opts_SetSpeed(float val)
+{
+  if (val < MINIMUM_SPEED)
+  {
+    val = MINIMUM_SPEED;
+    fprintf(stderr,"Warning: requested speed below minimum, setting to %g.\n",MINIMUM_SPEED);
+  }
+  if (val > MAX_MAX_SPEED)
+  {
+    val = MAX_MAX_SPEED;
+    fprintf(stderr,"Warning: requested speed above Tuxmath's maximum, setting to %g.\n",MAX_MAX_SPEED);
+  }
+  if (val > Opts_MaxSpeed())
+  {
+    val = Opts_MaxSpeed();
+    fprintf(stderr,"Warning: requested speed above currently selected maximum, setting to %g.\n",
+            Opts_MaxSpeed());
+  }
+  game_options->speed = val;
+}
+
+
+void Opts_SetAllowSpeedup(int val)
+{
+  game_options->allow_speedup = int_to_bool(val);
+}
+
+
+void Opts_SetSpeedupFactor(float val)
+{
+  if (val < MIN_SPEEDUP_FACTOR)
+  {
+    val = MIN_SPEEDUP_FACTOR;
+    fprintf(stderr,"Warning: requested speedup factor below Tuxmath's minimum, setting to %g.\n",MIN_SPEEDUP_FACTOR);
+  }
+  if (val > MAX_SPEEDUP_FACTOR)
+  {
+    val = MAX_SPEEDUP_FACTOR;
+    fprintf(stderr,"Warning: requested speedup factor above Tuxmath's maximum, setting to %g.\n",MAX_SPEEDUP_FACTOR);
+  }
+  game_options->speedup_factor = val;
+}
+
+
+void Opts_SetMaxSpeed(float val)
+{
+  if (val < MINIMUM_SPEED)
+  {
+    val = MINIMUM_SPEED;
+    fprintf(stderr,"Warning: requested max speed below minimum, setting to %g.\n",
+            MINIMUM_SPEED);
+  }
+  if (val > MAX_MAX_SPEED)
+  {
+    val = MAX_MAX_SPEED;
+    fprintf(stderr,"Warning: requested max speed above Tuxmath's maximum, setting to %g.\n",
+            MAX_MAX_SPEED);
+  }
+  if (val < Opts_Speed())
+  {
+    val = Opts_Speed();
+    fprintf(stderr,"Warning: requested max speed less than current speed, setting to %g.\n",
+            Opts_MaxSpeed());
+  }
+  game_options->max_speed = val;
+}
+
+
+void Opts_SetSlowAfterWrong(int val)
+{
+  game_options->slow_after_wrong = int_to_bool(val);
+}
+
+
+void Opts_SetStartingComets(int val)
+{
+  if (val < MIN_COMETS)
+  {
+    val = MIN_COMETS;
+    fprintf(stderr,"Warning: requested starting comets below Tuxmath's minimum, setting to %d.\n",
+            MIN_COMETS);
+  }
+  if (val > MAX_MAX_COMETS)
+  {
+    val = MAX_MAX_COMETS;
+    fprintf(stderr,"Warning: requested starting comets above Tuxmath's maximum, setting to %d.\n",
+            MAX_MAX_COMETS);
+  }
+  if (val > Opts_MaxComets())
+  {
+    val = Opts_MaxComets();
+    fprintf(stderr,"Warning: requested starting comets above currently selected maximum, setting to %d.\n",
+            Opts_MaxComets());
+  }
+  game_options->starting_comets = val;
+}
+
+
+void Opts_SetExtraCometsPerWave(int val)
+{
+  if (val < 0)
+  {
+    val = 0;
+    fprintf(stderr,"Warning: requested extra comets below Tuxmath's minimum, setting to %d.\n",
+            0);
+  }
+  if (val > MAX_MAX_COMETS)
+  {
+    val = MAX_MAX_COMETS;
+    fprintf(stderr,"Warning: requested extra comets above Tuxmath's maximum, setting to %d.\n",
+            MAX_MAX_COMETS);
+  }
+  if (val > Opts_MaxComets())
+  {
+    val = Opts_MaxComets();
+    fprintf(stderr,"Warning: requested extra comets above currently selected maximum, setting to %d.\n",
+            Opts_MaxComets());
+  }
+  game_options->extra_comets_per_wave = val;
+}
+
+
+void Opts_SetMaxComets(int val)
+{
+  if (val < MIN_COMETS)
+  {
+    val = MIN_COMETS;
+    fprintf(stderr,"Warning: requested max comets below Tuxmath's minimum, setting to %d.\n",
+            MIN_COMETS);
+  }
+  if (val > MAX_MAX_COMETS)
+  {
+    val = MAX_MAX_COMETS;
+    fprintf(stderr,"Warning: requested max comets above Tuxmath's maximum, setting to %d.\n",
+            MAX_MAX_COMETS);
+  }
+  game_options->max_comets = val;
+}
+
+
+void Opts_SetNextMission(char* str)
+{
+  int len = strlen(str);
+  if (len < PATH_MAX)
+  {
+    strcpy(game_options->next_mission, str);
+  }
+  else
+  {
+    fprintf(stderr,"Warning: Opts_SetNextMission() - string invalid or overflow\n");
+  }
+}
+
+
+void Opts_SetSaveSummary(int val)
+{
+  game_options->save_summary = int_to_bool(val);
+}
+
+
+void Opts_SetUseFeedback(int val)
+{
+  game_options->use_feedback = int_to_bool(val);
+}
+
+
+void Opts_SetDangerLevel(float val)
+{
+  if (val < 0)
+  {
+    val = 0;
+    fprintf(stderr,"Warning: danger level must be between 0 and 1, setting to 0.\n");
+  }
+  if (val > 1)
+  {
+    val = 1;
+    fprintf(stderr,"Warning: danger level must be between 0 and 1, setting to 1.\n");
+  }
+  game_options->danger_level = val;
+}
+
+
+void Opts_SetDangerLevelSpeedup(float val)
+{
+      if (val < 1)
+      {
+        val = 1;
+        fprintf(stderr,"Warning: danger_level_speedup must be at least 1, setting to 1.\n");
+      }
+  game_options->danger_level_speedup = val;
+}
+
+
+void Opts_SetDangerLevelMax(float val)
+{
+  if (val < 0)
+  {
+    val = 0;
+    fprintf(stderr,"Warning: danger level max must be between 0 and 1, setting to 0.\n");
+  }
+  if (val > 1)
+  {
+    val = 1;
+    fprintf(stderr,"Warning: danger level max must be between 0 and 1, setting to 1.\n");
+  }
+  game_options->danger_level_max = val;
+}
+
+
+void Opts_SetCityExplHandicap(float val)
+{
+  if (val < 0)
+  {
+    val = 0;
+    fprintf(stderr,"Warning: city_explode_handicap level set below minimum, setting to 0.\n");
+  }
+  game_options->city_expl_handicap = val;
+}
+
+
+
+/* whether sound system is successfully initialized and sound files loaded: */
+/* this flag is set by the program, not the user, and is not in the config file. */
+void Opts_SetSoundHWAvailable(int val)
+{
+  game_options->sound_hw_available = int_to_bool(val);
+}
+
+/* Allows game() to store score for high score table code: */
+void Opts_SetLastScore(int val)
+{
+  game_options->last_score = val;
+}
+
+void Opts_SetKeepScore(int val)
+{
+  game_options->keep_score = val;
+}
+
+
+/* "Get" functions for tuxmath options struct: */
+//int Opts_PerUserConfig(void)
+//{
+//  if (!game_options)
+//  {
+//    fprintf(stderr, "\nOpts_PerUserConfig(): game_options not valid!\n");
+//    return GAME_OPTS_INVALID;
+//  }
+//  return global_options->iopts[PER_USER_CONFIG];
+//}
+//
+//
+//int Opts_UseSound(void)
+//{
+//  if (!game_options)
+//  {
+//    fprintf(stderr, "\nOpts_UseSound(): game_options not valid!\n");
+//    return GAME_OPTS_INVALID;
+//  }
+//  return global_options->iopts[USE_SOUND] > 0;
+//}
+//
+//
+//int Opts_MenuSound(void)
+//{
+//  if (!game_options)
+//  {
+//    fprintf(stderr, "\nOpts_MenuSound(): game_options not valid!\n");
+//    return GAME_OPTS_INVALID;
+//  }
+//  return global_options->iopts[MENU_SOUND];
+//}
+//
+//
+//int Opts_MenuMusic(void)
+//{
+//  if (!game_options)
+//  {
+//    fprintf(stderr, "\nOpts_MenuMusic(): game_options not valid!\n");
+//    return GAME_OPTS_INVALID;
+//  }
+//  return global_options->iopts[MENU_MUSIC];
+//}
+//
+//
+//int Opts_Fullscreen(void)
+//{
+//  if (!game_options)
+//  {
+//    fprintf(stderr, "\nOpts_Fullscreen(): game_options not valid!\n");
+//    return GAME_OPTS_INVALID;
+//  }
+//  return global_options->iopts[FULLSCREEN];
+//}
+
+
+int Opts_UseBkgd(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_UserBkgd(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->use_bkgd;
+}
+
+
+int Opts_HelpMode(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_HelpMode(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->help_mode;
+}
+
+
+int Opts_DemoMode(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_DemoMode(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->demo_mode;
+}
+
+
+int Opts_OperOverride(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_OperOverride(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->oper_override;
+}
+
+
+//int Opts_UseKeypad(void)
+//{
+//  if (!game_options)
+//  {
+//    fprintf(stderr, "\nOpts_UseKeypad(): game_options not valid!\n");
+//    return GAME_OPTS_INVALID;
+//  }
+//  return global_options->iopts[USE_KEYPAD];
+//}
+//
+//
+int Opts_AllowPause(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_AllowPause(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->allow_pause;
+}
+
+
+//int Opts_UseIgloos(void)
+//{
+//  if (!game_options)
+//  {
+//    fprintf(stderr, "\nOpts_UseIgloos(): game_options not valid!\n");
+//    return GAME_OPTS_INVALID;
+//  }
+//  return global_options->iopts[USE_IGLOOS];
+//}
+
+int Opts_BonusCometInterval(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_BonusCometInterval(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->bonus_comet_interval;
+}
+
+
+float Opts_BonusSpeedRatio(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_BonusSpeedRatio(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->bonus_speed_ratio;
+}
+
+
+float Opts_Speed(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_Speed(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->speed;
+}
+
+
+int Opts_AllowSpeedup(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_AllowSpeedup(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->allow_speedup;
+}
+
+
+float Opts_SpeedupFactor(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_SpeedupFactor(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->speedup_factor;
+}
+
+
+float Opts_MaxSpeed(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_MaxSpeed(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->max_speed;
+}
+
+
+int Opts_SlowAfterWrong(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_SlowAfterWrong(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->slow_after_wrong;
+}
+
+
+int Opts_StartingComets(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_StartingComets(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->starting_comets;
+}
+
+
+int Opts_ExtraCometsPerWave(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_ExtraCometsPerWave(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->extra_comets_per_wave;
+}
+
+
+int Opts_MaxComets(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_MaxComets(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->max_comets;
+}
+
+/* FIXME maybe not good idea to have a malloc() in a function like this: */
+char* Opts_NextMission(void)
+{
+  char* str;
+  int length;
+  length = strlen(game_options->next_mission);
+  str = malloc((length * sizeof(char)) + 1);
+  strcpy(str, game_options->next_mission);
+  return str;
+}
+
+
+int Opts_SaveSummary(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_SaveSummary(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->save_summary;
+}
+
+
+int Opts_LastScore(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_LastScore(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->last_score;
+}
+
+
+int Opts_UseFeedback(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_UseFeedback(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->use_feedback;
+}
+
+
+float Opts_DangerLevel(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_DangerLevel(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->danger_level;
+}
+
+
+float Opts_DangerLevelSpeedup(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_DangerLevelSpeedup(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->danger_level_speedup;
+}
+
+
+float Opts_DangerLevelMax(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_DangerLevelMax(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->danger_level_max;
+}
+
+
+float Opts_CityExplHandicap(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_CityExplHandicap(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->city_expl_handicap;
+}
+
+
+
+/* whether sound system is successfully initialized and sound files loaded: */
+/* this flag is set by the program, not the user, and is not in the config file. */
+int Opts_SoundHWAvailable(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_SoundHWAvailable(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->sound_hw_available;
+}
+
+
+/* Returns true if only if the player wants to use sound */
+/* and the sound system is actually available:           */
+int Opts_UsingSound(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_UsingSound(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return (global_options->iopts[USE_SOUND]>0 && game_options->sound_hw_available);
+}
+
+int Opts_KeepScore(void)
+{
+  if (!game_options)
+  {
+    fprintf(stderr, "\nOpts_KeepScore(): game_options not valid!\n");
+    return GAME_OPTS_INVALID;
+  }
+  return game_options->keep_score;
+}
+/********************************************************************/
+/*  "private methods" (static functions only visible in options.c)  */
+/********************************************************************/
+
+
+
+
+
+/* to prevent option settings in math_opts from getting set to */
+/* values other than 0 or 1                                    */
+int int_to_bool(int i)
+{
+  if (i)
+    return 1;
+  else
+    return 0;
+}
+
+///* determine which option class a name belongs to, and set it */
+///* accordingly. Returns 1 on success, 0 on failure            */
+//static int find_and_set_option(const char* name, int val)
+//{
+//  int index = -1;
+//  
+//  if ((index = MC_MapTextToIndex(name)) != -1) //is it a math opt?
+//    MC_SetOpt(index, val);
+//  else if ((index = Opts_MapTextToIndex(name)) != -1) //is it a global opt?
+//    Opts_SetGlobalOpt(index, val);
+//  else //no? oh well.
+//    return 0;
+//    
+//  return 1;
+//}
+  
+/* prints struct to stream: */
+void print_game_options(FILE* fp, int verbose)
+{
+ /* bail out if no struct */
+  if (!game_options)
+  {
+    fprintf(stderr, "print_game_options(): invalid game_option_type struct");
+    return;
+  }
+
+  if(verbose)
+  {
+    fprintf (fp, "\n############################################################\n" 
+                 "#                                                          #\n"
+                 "#                 General Game Options                     #\n"
+                 "#                                                          #\n"
+                 "# The following options are boolean (true/false) variables #\n"
+                 "# that control various aspects of Tuxmath's behavior.      #\n"
+                 "# The program writes the values to the file as either '0'  #\n"
+                 "# or '1'. However, the program accepts 'n', 'no', 'f', and #\n"
+                 "# 'false' as synonyms for '0', and similarly accepts 'y',  #\n"
+                 "# 'yes', 't', and 'true' as synonyms for '1' (all case-    #\n"
+                 "# insensitive).                                            #\n"
+                 "############################################################\n\n");
+  }
+
+  if(verbose)
+  {
+    fprintf (fp, "############################################################\n" 
+                 "# 'PER_USER_CONFIG' determines whether Tuxmath will look   #\n"
+                 "# in the user's home directory for settings. Default is 1  #\n"
+                 "# (yes). If deselected, the program will ignore the user's #\n"
+                 "# .tuxmath file and use the the global settings in the     #\n"
+                 "# installation-wide config file.                           #\n"
+                 "# This setting cannot be changed by an ordinary user.      #\n"
+                 "############################################################\n");
+  }
+  fprintf(fp, "PER_USER_CONFIG = %d\n", global_options->iopts[PER_USER_CONFIG]);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Self-explanatory, default is 1:\n");
+  }
+  fprintf(fp, "USE_SOUND = %d\n", global_options->iopts[USE_SOUND]>0);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Use FULLSCREEN at 640x480 resolution instead of\n"
+                 "640x480 window. Default is 1 (FULLSCREEN). Change to 0\n"
+                 "if SDL has trouble with FULLSCREEN on your system.\n");
+  } 
+  fprintf(fp, "FULLSCREEN = %d\n", global_options->iopts[FULLSCREEN]);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Use 640x480 jpg image for background; default is 1.\n");
+  }
+  fprintf(fp, "use_bkgd = %d\n", game_options->use_bkgd);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Program runs as demo; default is 0.\n");
+  }
+  fprintf(fp, "demo_mode = %d\n", game_options->demo_mode);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Use operator selection from command line; default is 0.\n");
+  }
+  fprintf(fp, "oper_override = %d\n", game_options->oper_override);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Display onscreen numeric keypad; default is 0.\n");
+  }
+  fprintf(fp, "USE_KEYPAD = %d\n", global_options->iopts[USE_KEYPAD]);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n############################################################\n" 
+                 "# The next settings determine the speed and number         #\n"
+                 "# of comets.  The speed settings are float numbers (mean-  #\n"
+                 "# ing decimals allowed). The comet settings are integers.  #\n"
+                 "#                                                          #\n"
+                 "# Starting comet speed and max comet speed are generally   #\n"
+                 "# applicable. The main choice is whether you want to use   #\n"
+                 "# feedback, i.e., to adjust the speed automatically based  #\n"
+                 "# on the player's performance.                             #\n"
+                 "#                                                          #\n"
+                 "# Without feedback, the speed increases by a user-         #\n"
+                 "# settable factor ('speedup_factor'), with an option       #\n"
+                 "# ('slow_after_wrong') to go back to the starting speed    #\n"
+                 "# when a city gets hit.                                    #\n"
+                 "#                                                          #\n"
+                 "# With feedback, you set a desired 'danger level,' which   #\n"
+                 "# determines how close the comets should typically         #\n"
+                 "# approach the cities before the player succeeds in        #\n"
+                 "# destroying them.  The game will adjust its speed         #\n"
+                 "# accordingly, getting faster when the player is easily    #\n"
+                 "# stopping the comets, and slowing down when there are     #\n"
+                 "# too many close calls or hits. You can also have the      #\n"
+                 "# danger level increase with each wave.                    #\n"
+                 "############################################################\n");
+  }
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Whether to increase speed and number of comets with \n"
+                 "# each wave.  May want to turn this off for smaller kids.\n"
+                 "# Default is 1 (allow game to speed up)\n");
+  }
+  fprintf(fp, "allow_speedup = %d\n", game_options->allow_speedup);
+
+
+  fprintf(fp, "slow_after_wrong = %d\n", game_options->slow_after_wrong);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Starting comet speed. Default is 1.\n");
+  }
+  fprintf(fp, "speed = %f\n", game_options->speed);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# If feedback is not used but 'allow_speedup' is\n"
+                 "# enabled, the comet speed will be\n"
+                 "# multiplied by this factor with each new wave.\n"
+                 "# Default is 1.2 (i.e. 20 percent increase per wave)\n");
+  }
+  fprintf(fp, "speedup_factor = %f\n", game_options->speedup_factor);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Maximum speed. Default is 10.\n");
+  }
+  fprintf(fp, "max_speed = %f\n", game_options->max_speed);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Number of comets for first wave. Default is 2.\n");
+  }
+  fprintf(fp, "starting_comets = %d\n", game_options->starting_comets);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Comets to add for each successive wave. Default is 2.\n");
+  }
+  fprintf(fp, "extra_comets_per_wave = %d\n", game_options->extra_comets_per_wave);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# Maximum number of comets. Default is 10.\n");
+  }
+  fprintf(fp, "max_comets = %d\n", game_options->max_comets);
+
+  if(verbose)
+  {
+     fprintf (fp, "\n# Use feedback? Default (for now) is false, 0.\n");
+  }
+  fprintf(fp, "use_feedback = %d\n", game_options->use_feedback);
+
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# (Feedback) Set the desired danger level.\n"
+            "# 0 = too safe, comets typically exploded right at the very top\n"
+            "# 1 = too dangerous, comets typically exploded at the moment they hit cities\n"
+            "# Set it somewhere between these extremes. As a guideline, early\n"
+            "# elementary kids might feel comfortable around 0.2-0.3, older kids\n"
+            "# at around 0.4-0.6. Default 0.35.\n");
+  }
+  fprintf(fp, "danger_level = %f\n", game_options->danger_level);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# (Feedback) Set danger level speedup.\n"
+                 "# The margin of safety will decrease by this factor each wave.\n"
+                 "# Default 1.1. Note 1 = no increase in danger level.\n");
+  }
+  fprintf(fp, "danger_level_speedup = %f\n", game_options->danger_level_speedup);
+
+  if(verbose)
+  {
+    fprintf (fp, "\n# (Feedback) Set the maximum danger level.\n"
+                 "# Default 0.9.\n");
+  }
+  fprintf(fp, "danger_level_max = %f\n", game_options->danger_level_max);
+
+  if (verbose)
+  { 
+    fprintf (fp, "\n# (Feedback) Set the handicap for hitting cities.\n"
+                 "# When bigger than 0, this causes the game to slow down\n"
+                 "# by an extra amount after a wave in which one or more\n"
+                 "# cities get hit. Note that this is similar to slow_after_wrong,\n"
+                 "# but allows for more gradual changes.\n"
+                 "# Default 0 (no extra handicap).\n");
+  }
+  fprintf(fp, "city_explode_handicap = %f\n", game_options->city_expl_handicap);
+
+/*
+  fprintf(fp, "num_cities = %d\n", game_options->num_cities);
+  fprintf(fp, "num_bkgds = %d\n", game_options->num_bkgds);
+  fprintf(fp, "max_city_colors = %d\n", game_options->max_city_colors);
+*/
+}

Added: tuxmath/trunk/src/options.h
===================================================================
--- tuxmath/trunk/src/options.h	                        (rev 0)
+++ tuxmath/trunk/src/options.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,190 @@
+/*
+  options.h
+
+  For TuxMath
+  The options screen loop.
+
+  by Bill Kendrick
+  bill at newbreedsoftware.com
+  http://www.newbreedsoftware.com/
+
+
+  Part of "Tux4Kids" Project
+  http://www.tux4kids.org/
+      
+  August 26, 2001 - February 21, 2003
+
+  Extensively revised by David Bruce
+  2004-2007
+*/
+
+
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+#include "globals.h"  /* needed for PATH_MAX definition */
+
+enum {
+  PER_USER_CONFIG,
+  USE_SOUND,
+  MENU_SOUND,
+  MENU_MUSIC,
+  FULLSCREEN,
+  USE_KEYPAD,
+  USE_IGLOOS,
+  NUM_GLOBAL_OPTS
+};                                 
+
+extern const char* const OPTION_TEXT[];
+extern const int OPTION_DEFAULTS[];
+
+/* contains options that tend to apply to the progam as a whole, rather *
+ * than on a per-game basis                                             */
+typedef struct global_option_type {
+  int iopts[NUM_GLOBAL_OPTS];
+} global_option_type;
+
+/* this struct contains all options regarding general       */
+/* gameplay but not having to do with math questions per se */
+typedef struct game_option_type {
+  /* general game options */
+  int use_bkgd;
+  int help_mode;
+  int demo_mode;
+  int oper_override;
+  int allow_pause;
+  int bonus_comet_interval;
+  float bonus_speed_ratio;
+  float speed;
+  int allow_speedup;
+  float speedup_factor;
+  float max_speed;
+  int slow_after_wrong;
+  int starting_comets;
+  int extra_comets_per_wave;
+  int max_comets;  
+  char next_mission[PATH_MAX];
+  int save_summary;
+  int use_feedback;
+  float danger_level;
+  float danger_level_speedup;
+  float danger_level_max;
+  float city_expl_handicap;
+  
+  int mp_multiplayer;
+  int mp_round;
+  int mp_playernum;
+  
+  /* whether sound system is successfully initialized and sound files loaded: */
+  /* this flag is set by the program, not the user, and is not in the config file. */
+  int sound_hw_available;
+  /* place to save score of last game - not read in from file: */
+  int last_score;
+  /* not sure the rest of these belong in here */
+  int num_cities;  /* MUST BE AN EVEN NUMBER! */
+  int num_bkgds;
+  int max_city_colors;
+  int keep_score;
+} game_option_type;
+
+
+enum {
+  OPT_OP_ADD,
+  OPT_OP_SUB,
+  OPT_OP_MUL,
+  OPT_OP_DIV,
+  OPT_A_MAX,
+  OPT_A_SPEED,
+  OPT_Q_RANGE,
+  NUM_OPTS
+};
+
+/* global struct (until accessor functions completed) */
+//extern game_option_type* game_options; /* used by setup.c, options.c, game.c */
+
+/* main options function called from title(): */
+//int options(void);
+
+/* "Public methods" of game_option_type struct; program interacts with struct */
+/* through these simple functions (rather than directly) to allow for error   */
+/* checking, etc.                                                             */
+int Opts_Initialize(void);
+void Opts_Cleanup(void);
+
+
+/* "Set" functions for tuxmath options struct: */
+
+unsigned int Opts_MapTextToIndex(const char* text);
+
+int  Opts_GetGlobalOpt(unsigned int index);
+void Opts_SetGlobalOpt(unsigned int index, int val);
+
+void Opts_SetUseBkgd(int val);
+void Opts_SetHelpMode(int val);
+void Opts_SetDemoMode(int val);
+void Opts_SetOperOverride(int val);
+void Opts_SetAllowPause(int val);
+void Opts_SetBonusCometInterval(int val);
+void Opts_SetBonusSpeedRatio(float val);
+void Opts_SetSpeed(float val);
+void Opts_SetAllowSpeedup(int val);
+void Opts_SetSpeedupFactor(float val);
+void Opts_SetMaxSpeed(float val);
+void Opts_SetSlowAfterWrong(int val);
+void Opts_SetStartingComets(int val);
+void Opts_SetExtraCometsPerWave(int val);
+void Opts_SetMaxComets(int val);
+void Opts_SetNextMission(char* str);
+void Opts_SetSaveSummary(int val);
+void Opts_SetUseFeedback(int val);
+void Opts_SetDangerLevel(float val);
+void Opts_SetDangerLevelSpeedup(float val);
+void Opts_SetDangerLevelMax(float val);
+void Opts_SetCityExplHandicap(float val);
+
+/* whether sound system is successfully initialized and sound files loaded: */
+/* this flag is set by the program, not the user, and is not in the config file. */
+void Opts_SetSoundHWAvailable(int val);
+/* Used by high score table code, not config file: */
+void Opts_SetLastScore(int val);
+
+void Opts_SetKeepScore(int val);
+
+/* "Get" functions for tuxmath options struct: */
+int Opts_UseBkgd(void);
+int Opts_HelpMode(void);
+int Opts_DemoMode(void);
+int Opts_OperOverride(void);
+int Opts_AllowPause(void);
+int Opts_BonusCometInterval(void);
+float Opts_BonusSpeedRatio(void);
+float Opts_Speed(void);
+int Opts_AllowSpeedup(void);
+float Opts_SpeedupFactor(void);
+float Opts_MaxSpeed(void);
+int Opts_SlowAfterWrong(void);
+int Opts_StartingComets(void);
+int Opts_ExtraCometsPerWave(void);
+int Opts_MaxComets(void);
+char* Opts_NextMission(void);
+int Opts_SaveSummary(void);
+int Opts_UseFeedback(void);
+float Opts_DangerLevel(void);
+float Opts_DangerLevelSpeedup(void);
+float Opts_DangerLevelMax(void);
+float Opts_CityExplHandicap(void);
+int Opts_KeepScore(void);
+
+/* whether sound system is successfully initialized and sound files loaded: */
+/* this flag is set by the program, not the user, and is not in the config file. */
+int Opts_SoundHWAvailable(void);
+/* this is the function that says if sound is both desired and actually available: */
+int Opts_UsingSound(void);
+
+/* Returns score of last Arcade-type game this session: */
+int Opts_LastScore(void);
+
+/* print options values to stream - for debugging purposes - has been */
+/* superceded by write_config_file() to actually write human-readable file. */
+void print_game_options(FILE* fp, int verbose);
+#endif

Added: tuxmath/trunk/src/pixels.c
===================================================================
--- tuxmath/trunk/src/pixels.c	                        (rev 0)
+++ tuxmath/trunk/src/pixels.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,257 @@
+/*
+  pixels.c
+
+  For Tux Paint
+  Pixel read/write functions
+
+  Copyright (c) 2002-2006 by Bill Kendrick and others
+  bill at newbreedsoftware.com
+  http://www.newbreedsoftware.com/tuxpaint/
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+  (See COPYING.txt)
+
+  June 14, 2002 - February 17, 2006
+  $Id: pixels.c,v 1.3 2006/08/27 21:00:55 wkendrick Exp $
+*/
+
+#include "pixels.h"
+#include "compiler.h"
+//#include "debug.h"
+
+/* Draw a single pixel into the surface: */
+void putpixel8(SDL_Surface * surface, int x, int y, Uint32 pixel)
+{
+  Uint8 *p;
+
+  /* Assuming the X/Y values are within the bounds of this surface... */
+  if (likely
+      (likely((unsigned) x < (unsigned) surface->w)
+       && likely((unsigned) y < (unsigned) surface->h)))
+  {
+    // Set a pointer to the exact location in memory of the pixel
+    p = (Uint8 *) (((Uint8 *) surface->pixels) +        /* Start: beginning of RAM */
+                   (y * surface->pitch) +        /* Go down Y lines */
+                   x);                /* Go in X pixels */
+
+
+    /* Set the (correctly-sized) piece of data in the surface's RAM
+     *          to the pixel value sent in: */
+
+    *p = pixel;
+  }
+}
+
+/* Draw a single pixel into the surface: */
+void putpixel16(SDL_Surface * surface, int x, int y, Uint32 pixel)
+{
+  Uint8 *p;
+
+  /* Assuming the X/Y values are within the bounds of this surface... */
+  if (likely
+      (likely((unsigned) x < (unsigned) surface->w)
+       && likely((unsigned) y < (unsigned) surface->h)))
+  {
+    // Set a pointer to the exact location in memory of the pixel
+    p = (Uint8 *) (((Uint8 *) surface->pixels) +        /* Start: beginning of RAM */
+                   (y * surface->pitch) +        /* Go down Y lines */
+                   (x * 2));        /* Go in X pixels */
+
+
+    /* Set the (correctly-sized) piece of data in the surface's RAM
+     *          to the pixel value sent in: */
+
+    *(Uint16 *) p = pixel;
+  }
+}
+
+/* Draw a single pixel into the surface: */
+void putpixel24(SDL_Surface * surface, int x, int y, Uint32 pixel)
+{
+  Uint8 *p;
+
+  /* Assuming the X/Y values are within the bounds of this surface... */
+  if (likely
+      (likely((unsigned) x < (unsigned) surface->w)
+       && likely((unsigned) y < (unsigned) surface->h)))
+  {
+    // Set a pointer to the exact location in memory of the pixel
+    p = (Uint8 *) (((Uint8 *) surface->pixels) +        /* Start: beginning of RAM */
+                   (y * surface->pitch) +        /* Go down Y lines */
+                   (x * 3));        /* Go in X pixels */
+
+
+    /* Set the (correctly-sized) piece of data in the surface's RAM
+     *          to the pixel value sent in: */
+
+    if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
+    {
+      p[0] = (pixel >> 16) & 0xff;
+      p[1] = (pixel >> 8) & 0xff;
+      p[2] = pixel & 0xff;
+    }
+    else
+    {
+      p[0] = pixel & 0xff;
+      p[1] = (pixel >> 8) & 0xff;
+      p[2] = (pixel >> 16) & 0xff;
+    }
+
+  }
+}
+
+/* Draw a single pixel into the surface: */
+void putpixel32(SDL_Surface * surface, int x, int y, Uint32 pixel)
+{
+  Uint8 *p;
+
+  /* Assuming the X/Y values are within the bounds of this surface... */
+  if (likely
+      (likely((unsigned) x < (unsigned) surface->w)
+       && likely((unsigned) y < (unsigned) surface->h)))
+  {
+    // Set a pointer to the exact location in memory of the pixel
+    p = (Uint8 *) (((Uint8 *) surface->pixels) +        /* Start: beginning of RAM */
+                   (y * surface->pitch) +        /* Go down Y lines */
+                   (x * 4));        /* Go in X pixels */
+
+
+    /* Set the (correctly-sized) piece of data in the surface's RAM
+     *          to the pixel value sent in: */
+
+    *(Uint32 *) p = pixel;        // 32-bit display
+  }
+}
+
+/* Get a pixel: */
+Uint32 getpixel8(SDL_Surface * surface, int x, int y)
+{
+  Uint8 *p;
+
+  /* get the X/Y values within the bounds of this surface */
+  if (unlikely((unsigned) x > (unsigned) surface->w - 1u))
+    x = (x < 0) ? 0 : surface->w - 1;
+  if (unlikely((unsigned) y > (unsigned) surface->h - 1u))
+    y = (y < 0) ? 0 : surface->h - 1;
+
+  /* Set a pointer to the exact location in memory of the pixel
+     in question: */
+
+  p = (Uint8 *) (((Uint8 *) surface->pixels) +        /* Start at top of RAM */
+                 (y * surface->pitch) +        /* Go down Y lines */
+                 x);                /* Go in X pixels */
+
+
+  /* Return the correctly-sized piece of data containing the
+   * pixel's value (an 8-bit palette value, or a 16-, 24- or 32-bit
+   * RGB value) */
+
+  return (*p);
+}
+
+/* Get a pixel: */
+Uint32 getpixel16(SDL_Surface * surface, int x, int y)
+{
+  Uint8 *p;
+
+  /* get the X/Y values within the bounds of this surface */
+  if (unlikely((unsigned) x > (unsigned) surface->w - 1u))
+    x = (x < 0) ? 0 : surface->w - 1;
+  if (unlikely((unsigned) y > (unsigned) surface->h - 1u))
+    y = (y < 0) ? 0 : surface->h - 1;
+
+  /* Set a pointer to the exact location in memory of the pixel
+     in question: */
+
+  p = (Uint8 *) (((Uint8 *) surface->pixels) +        /* Start at top of RAM */
+                 (y * surface->pitch) +        /* Go down Y lines */
+                 (x * 2));        /* Go in X pixels */
+
+
+  /* Return the correctly-sized piece of data containing the
+   * pixel's value (an 8-bit palette value, or a 16-, 24- or 32-bit
+   * RGB value) */
+
+  return (*(Uint16 *) p);
+}
+
+/* Get a pixel: */
+Uint32 getpixel24(SDL_Surface * surface, int x, int y)
+{
+  Uint8 *p;
+  Uint32 pixel;
+
+  /* get the X/Y values within the bounds of this surface */
+  if (unlikely((unsigned) x > (unsigned) surface->w - 1u))
+    x = (x < 0) ? 0 : surface->w - 1;
+  if (unlikely((unsigned) y > (unsigned) surface->h - 1u))
+    y = (y < 0) ? 0 : surface->h - 1;
+
+  /* Set a pointer to the exact location in memory of the pixel
+     in question: */
+
+  p = (Uint8 *) (((Uint8 *) surface->pixels) +        /* Start at top of RAM */
+                 (y * surface->pitch) +        /* Go down Y lines */
+                 (x * 3));        /* Go in X pixels */
+
+
+  /* Return the correctly-sized piece of data containing the
+   * pixel's value (an 8-bit palette value, or a 16-, 24- or 32-bit
+   * RGB value) */
+
+  /* Depending on the byte-order, it could be stored RGB or BGR! */
+
+  if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
+    pixel = p[0] << 16 | p[1] << 8 | p[2];
+  else
+    pixel = p[0] | p[1] << 8 | p[2] << 16;
+
+  return pixel;
+}
+
+/* Get a pixel: */
+Uint32 getpixel32(SDL_Surface * surface, int x, int y)
+{
+  Uint8 *p;
+
+  /* get the X/Y values within the bounds of this surface */
+  if (unlikely((unsigned) x > (unsigned) surface->w - 1u))
+    x = (x < 0) ? 0 : surface->w - 1;
+  if (unlikely((unsigned) y > (unsigned) surface->h - 1u))
+    y = (y < 0) ? 0 : surface->h - 1;
+
+  /* Set a pointer to the exact location in memory of the pixel
+     in question: */
+
+  p = (Uint8 *) (((Uint8 *) surface->pixels) +        /* Start at top of RAM */
+                 (y * surface->pitch) +        /* Go down Y lines */
+                 (x * 4));        /* Go in X pixels */
+
+
+  /* Return the correctly-sized piece of data containing the
+   * pixel's value (an 8-bit palette value, or a 16-, 24- or 32-bit
+   * RGB value) */
+
+  return *(Uint32 *) p;                // 32-bit display
+}
+
+void (*putpixels[]) (SDL_Surface *, int, int, Uint32) =
+{
+putpixel8, putpixel8, putpixel16, putpixel24, putpixel32};
+
+
+Uint32(*getpixels[])(SDL_Surface *, int, int) =
+{
+getpixel8, getpixel8, getpixel16, getpixel24, getpixel32};

Added: tuxmath/trunk/src/pixels.h
===================================================================
--- tuxmath/trunk/src/pixels.h	                        (rev 0)
+++ tuxmath/trunk/src/pixels.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,49 @@
+/*
+  pixels.h
+
+  For Tux Paint
+  Pixel read/write functions
+
+  Copyright (c) 2002-2006 by Bill Kendrick and others
+  bill at newbreedsoftware.com
+  http://www.newbreedsoftware.com/tuxpaint/
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+  (See COPYING.txt)
+
+  June 14, 2002 - February 17, 2006
+  $Id: pixels.h,v 1.2 2006/08/27 21:00:55 wkendrick Exp $
+*/
+
+#ifndef PIXELS_H
+#define PIXELS_H
+
+#include "SDL.h"
+
+void putpixel8(SDL_Surface * surface, int x, int y, Uint32 pixel);
+void putpixel16(SDL_Surface * surface, int x, int y, Uint32 pixel);
+void putpixel24(SDL_Surface * surface, int x, int y, Uint32 pixel);
+void putpixel32(SDL_Surface * surface, int x, int y, Uint32 pixel);
+
+extern void (*putpixels[]) (SDL_Surface *, int, int, Uint32);
+
+Uint32 getpixel8(SDL_Surface * surface, int x, int y);
+Uint32 getpixel16(SDL_Surface * surface, int x, int y);
+Uint32 getpixel24(SDL_Surface * surface, int x, int y);
+Uint32 getpixel32(SDL_Surface * surface, int x, int y);
+
+extern Uint32(*getpixels[]) (SDL_Surface *, int, int);
+
+#endif

Added: tuxmath/trunk/src/scandir.c
===================================================================
--- tuxmath/trunk/src/scandir.c	                        (rev 0)
+++ tuxmath/trunk/src/scandir.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,264 @@
+
+/* Replacement scandir implementations to be used if Autoconf does not find scandir on */
+/* host platform - taken from Hatari project at Sourceforge - only modification is the */
+/* #ifndef HAVE_SCANDIR.  */
+
+/* We only need to compile this file if the host platform doesn't have scandir(): */
+#include "config.h"
+#ifndef HAVE_SCANDIR
+
+/*
+  Hatari - scandir.c
+
+  This file is distributed under the GNU Public License, version 2 or at
+  your option any later version. Read the file gpl.txt for details.
+
+  scandir function for BEOS, SunOS etc..
+*/
+const char ScanDir_rcsid[] = "Hatari $Id: scandir.c,v 1.3 2007/01/16 18:42:59 thothy Exp $";
+
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "scandir.h"
+
+/*-----------------------------------------------------------------------
+ * Here come alphasort and scandir for BeOS and SunOS
+ *-----------------------------------------------------------------------*/
+#if defined(__BEOS__) || (defined(__sun) && defined(__SVR4))
+
+#undef DIRSIZ
+
+#define DIRSIZ(dp)                                          \
+                ((sizeof(struct dirent) - sizeof(dp)->d_name) +     \
+                (((dp)->d_reclen + 1 + 3) &~ 3))
+
+#if defined(__sun) && defined(__SVR4)
+# define dirfd(d) ((d)->dd_fd)
+#elif defined(__BEOS__)
+#ifndef (__HAIKU__)
+#else
+# define dirfd(d) ((d)->fd)
+#endif
+#endif
+
+/
+/*-----------------------------------------------------------------------*/
+/**
+ * Alphabetic order comparison routine.
+ */
+int alphasort(const void *d1, const void *d2)
+{
+  return strcmp((*(struct dirent * const *)d1)->d_name, (*(struct dirent * const *)d2)->d_name);
+}
+
+
+/*-----------------------------------------------------------------------*/
+/**
+ * Scan a directory for all its entries
+ */
+int scandir(const char *dirname, struct dirent ***namelist, int (*sdfilter)(struct dirent *), int (*dcomp)(const void *, const void *))
+{
+  struct dirent *d, *p, **names;
+  struct stat stb;
+  size_t nitems;
+  size_t arraysz;
+  DIR *dirp;
+
+  if ((dirp = opendir(dirname)) == NULL)
+    return(-1);
+
+  if (fstat(dirfd(dirp), &stb) < 0)
+    return(-1);
+
+  /*
+   * estimate the array size by taking the size of the directory file
+   * and dividing it by a multiple of the minimum size entry.
+   */
+  arraysz = (stb.st_size / 24);
+
+  names = (struct dirent **)malloc(arraysz * sizeof(struct dirent *));
+  if (names == NULL)
+    return(-1);
+
+  nitems = 0;
+
+  while ((d = readdir(dirp)) != NULL)
+  {
+
+    if (sdfilter != NULL && !(*sdfilter)(d))
+      continue;       /* just selected names */
+
+    /*
+     * Make a minimum size copy of the data
+     */
+
+    p = (struct dirent *)malloc(DIRSIZ(d));
+    if (p == NULL)
+      return(-1);
+
+    p->d_ino = d->d_ino;
+    p->d_reclen = d->d_reclen;
+    /*p->d_namlen = d->d_namlen;*/
+    memcpy(p->d_name, d->d_name, p->d_reclen + 1);
+
+    /*
+     * Check to make sure the array has space left and
+     * realloc the maximum size.
+     */
+
+    if (++nitems >= arraysz)
+    {
+
+      if (fstat(dirfd(dirp), &stb) < 0)
+        return(-1);     /* just might have grown */
+
+      arraysz = stb.st_size / 12;
+
+      names = (struct dirent **)realloc((char *)names, arraysz * sizeof(struct dirent *));
+      if (names == NULL)
+        return(-1);
+    }
+
+    names[nitems-1] = p;
+  }
+
+  closedir(dirp);
+
+  if (nitems && dcomp != NULL)
+    qsort(names, nitems, sizeof(struct dirent *), dcomp);
+
+  *namelist = names;
+
+  return nitems;
+}
+
+
+#endif /* __BEOS__ || __sun */
+
+
+/*-----------------------------------------------------------------------
+* Here come alphasort and scandir for Windows
+*-----------------------------------------------------------------------*/
+#if defined(WIN32)
+
+#undef DATADIR     // stupid windows.h defines DATADIR, too
+#include <windows.h>
+
+/*-----------------------------------------------------------------------*/
+/**
+* Alphabetic order comparison routine.
+*/
+int alphasort(const void *d1, const void *d2)
+{
+  return stricmp((*(struct dirent * const *)d1)->d_name, (*(struct dirent * const *)d2)->d_name);
+}
+
+/*-----------------------------------------------------------------------*/
+/**
+* Scan a directory for all its entries
+*/
+int scandir(const char *dirname, struct dirent ***namelist, int (*sdfilter)(struct dirent *), int (*dcomp)(const void *, const void *))
+{
+  int len;
+  char *findIn, *d;
+  WIN32_FIND_DATA find;
+  HANDLE h;
+  int nDir = 0, NDir = 0;
+  struct dirent **dir = 0, *selectDir;
+  unsigned long ret;
+
+  len    = strlen(dirname);
+  findIn = (char *)malloc(len+5);
+  strcpy(findIn, dirname);
+  printf("scandir : findIn orign=%s\n", findIn);
+  for (d = findIn; *d; d++)
+    if (*d=='/')
+      *d='\\';
+  if ((len==0))
+  {
+    strcpy(findIn, ".\\*");
+  }
+  if ((len==1)&& (d[-1]=='.'))
+  {
+    strcpy(findIn, ".\\*");
+  }
+  if ((len>0) && (d[-1]=='\\'))
+  {
+    *d++ = '*';
+    *d = 0;
+  }
+  if ((len>1) && (d[-1]=='.') && (d[-2]=='\\'))
+  {
+    d[-1] = '*';
+  }
+  if ((len>1) && (d[-2]!='\\') && (d[-1]!='*'))
+  {
+    *d++ = '\\';
+    *d++ = '*';
+    *d = 0;
+  }
+
+  printf("scandir : findIn processed=%s\n", findIn);
+  if ((h=FindFirstFile(findIn, &find))==INVALID_HANDLE_VALUE)
+  {
+    printf("scandir : FindFirstFile error\n");
+    ret = GetLastError();
+    if (ret != ERROR_NO_MORE_FILES)
+    {
+      // TODO: return some error code
+    }
+    *namelist = dir;
+    return nDir;
+  }
+  do
+  {
+    printf("scandir : findFile=%s\n", find.cFileName);
+    selectDir=(struct dirent*)malloc(sizeof(struct dirent)+strlen(find.cFileName));
+    strcpy(selectDir->d_name, find.cFileName);
+    if (!sdfilter || (*sdfilter)(selectDir))
+    {
+      if (nDir==NDir)
+      {
+        struct dirent **tempDir = (struct dirent **)calloc(sizeof(struct dirent*), NDir+33);
+        if (NDir)
+          memcpy(tempDir, dir, sizeof(struct dirent*)*NDir);
+        if (dir)
+          free(dir);
+        dir = tempDir;
+        NDir += 32;
+      }
+      dir[nDir] = selectDir;
+      nDir++;
+      dir[nDir] = 0;
+    }
+    else
+    {
+      free(selectDir);
+    }
+  }
+  while (FindNextFile(h, &find));
+  ret = GetLastError();
+  if (ret != ERROR_NO_MORE_FILES)
+  {
+    // TODO: return some error code
+  }
+  FindClose(h);
+
+  free (findIn);
+
+  if (dcomp)
+    qsort (dir, nDir, sizeof(*dir),dcomp);
+
+  *namelist = dir;
+  return nDir;
+}
+
+#endif /* WIN32 */
+#endif /* HAVE_SCANDIR */

Added: tuxmath/trunk/src/scandir.h
===================================================================
--- tuxmath/trunk/src/scandir.h	                        (rev 0)
+++ tuxmath/trunk/src/scandir.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,27 @@
+/* Brought to tuxmath from the Hatari project under the terms of the GPLv2+ */
+/* It should only be included if scandir is not found by Autoconf - e.g.: */
+/* #ifndef HAVE_SCANDIR (from autoconf's config.h)    - DSB               */
+
+/*
+  Hatari - scandir.h
+
+  This file is distributed under the GNU Public License, version 2 or at
+  your option any later version. Read the file gpl.txt for details.
+*/
+#ifndef HATARI_SCANDIR_H
+#define HATARI_SCANDIR_H
+
+#include <dirent.h>
+
+#ifdef QNX
+#include <sys/types.h>
+#include <sys/dir.h>
+#define dirent direct
+#endif
+
+#if defined(__BEOS__) || (defined(__sun) && defined(__SVR4)) || defined(WIN32)
+extern int alphasort(const void *d1, const void *d2);
+extern int scandir(const char *dirname, struct dirent ***namelist, int (*sdfilter)(struct dirent *), int (*dcomp)(const void *, const void *));
+#endif
+
+#endif /* HATARI_SCANDIR_H */

Added: tuxmath/trunk/src/setup.c
===================================================================
--- tuxmath/trunk/src/setup.c	                        (rev 0)
+++ tuxmath/trunk/src/setup.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,827 @@
+/*
+  setup.c
+
+  For TuxMath
+  Contains some globals (screen surface, images, some option flags, etc.)
+  as well as the function to load data files (images, sounds, music)
+  and display a "Loading..." screen.
+
+  by Bill Kendrick
+  bill at newbreedsoftware.com
+  http://www.newbreedsoftware.com/
+
+  Modified by David Bruce
+  dbruce at tampabay.rr.com
+
+  Part of "Tux4Kids" Project
+  http://www.tux4kids.com/
+  Subversion repository:
+  https://svn.debian.alioth.org/tux4kids/tuxmath/
+
+
+  August 26, 2001 - Sept 18, 2007.
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* FIXME maybe unistd.h not needed, even less sure about portability */
+//#include <unistd.h>
+
+#include "SDL.h"
+#include "SDL_ttf.h"
+
+#ifndef NOSOUND
+#include "SDL_mixer.h"
+#endif
+
+#include "SDL_image.h"
+
+#include "options.h"
+#include "tuxmath.h"
+#include "mathcards.h"
+#include "setup.h"
+#include "fileops.h"
+#include "loaders.h"
+#include "game.h"
+#include "titlescreen.h"
+#include "highscore.h"
+#include "SDL_extras.h"
+
+#ifdef LINEBREAK
+#include "linewrap.h"
+#endif
+
+/* Global data used in setup.c:              */
+/* (These are now 'extern'd in "tuxmath.h") */
+
+int fs_res_x = RES_X;
+int fs_res_y = RES_Y;
+
+SDL_Surface* screen;
+SDL_Surface* images[NUM_IMAGES];
+/* Need special handling to generate flipped versions of images. This
+   is a slightly ugly hack arising from the use of the enum trick for
+   NUM_IMAGES. */
+#define NUM_FLIPPED_IMAGES 6
+SDL_Surface* flipped_images[NUM_FLIPPED_IMAGES];
+int flipped_img_lookup[NUM_IMAGES];
+SDL_Surface* blended_igloos[NUM_BLENDED_IGLOOS];
+
+const int flipped_img[] = {
+  IMG_PENGUIN_WALK_ON1,
+  IMG_PENGUIN_WALK_ON2,
+  IMG_PENGUIN_WALK_ON3,
+  IMG_PENGUIN_WALK_OFF1,
+  IMG_PENGUIN_WALK_OFF2,
+  IMG_PENGUIN_WALK_OFF3
+};
+
+
+#ifndef NOSOUND
+Mix_Chunk* sounds[NUM_SOUNDS];
+Mix_Music* musics[NUM_MUSICS];
+#endif
+
+
+/* Local function prototypes: */
+void initialize_options(void);
+void handle_command_args(int argc, char* argv[]);
+void initialize_SDL(void);
+void load_data_files(void);
+void generate_flipped_images(void);
+void generate_blended_images(void);
+
+//int initialize_game_options(void);
+void seticon(void);
+void usage(int err, char * cmd);
+
+void cleanup_memory(void);
+
+
+
+/* --- Set-up function - now in four easier-to-digest courses! --- */
+/* --- Er - make that six courses! --- */
+/* --- Six is right out. Seven is much better. --- */
+void setup(int argc, char * argv[])
+{
+  /* initialize settings and read in config files: */
+  /* Note this now only does the global settings   */
+  initialize_options();
+  /* Command-line code now in own function: */
+  handle_command_args(argc, argv);
+  /* Allocate memory for line wrapping */
+#ifdef LINEBREAK
+  linewrap_initialize();
+#endif
+  /* SDL setup in own function:*/
+  initialize_SDL();
+  /* Read image and sound files: */
+  load_data_files();
+ /* Generate flipped versions of walking images */
+  generate_flipped_images();
+  /* Generate blended images (e.g., igloos) */
+  generate_blended_images();
+  /* Note that the per-user options will be set after the call to
+     titlescreen, to allow for user-login to occur. 
+     
+     FIXME this means that command-line args will be overridden!
+     Is this desirable? */
+}
+
+
+
+
+/* Set up mathcards with default values for math question options, */
+/* set up game_options with defaults for general game options,     */
+/* then read in global config file                                 */
+void initialize_options(void)
+{
+  /* Initialize MathCards backend for math questions: */
+  if (!MC_Initialize())
+  {
+    printf("\nUnable to initialize MathCards\n");
+    fprintf(stderr, "\nUnable to initialize MathCards\n");
+    exit(1);
+  }
+
+  /* initialize game_options struct with defaults DSB */
+  if (!Opts_Initialize())
+  {
+    fprintf(stderr, "\nUnable to initialize game_options\n");
+    cleanup_on_error();
+    exit(1);
+  }
+
+  /* Now that MathCards and game_options initialized using  */
+  /* hard-coded defaults, read options from disk and mofify */
+  /* as needed. First read in installation-wide settings:   */
+  if (!read_global_config_file())
+  {
+    fprintf(stderr, "\nCould not find global config file.\n");
+    /* can still proceed using hard-coded defaults.         */
+  }
+}
+
+/* Read in the user-specific options (if desired)              */
+/* This has been split from the above to allow it to be called */
+/* from titlescreen, to allow for user-login to occur.         */
+void initialize_options_user(void)
+{
+  /* Read in user-specific settings, if desired.  By    */
+  /* default, this restores settings from the player's last */
+  /* game:                                                  */
+  if (Opts_GetGlobalOpt(PER_USER_CONFIG))
+  {
+    if (!read_user_config_file())
+    {
+      fprintf(stderr, "\nCould not find user's config file.\n");
+      /* can still proceed using hard-coded defaults.         */
+    }
+
+    /* If game being run for first time, try to write file: */
+    if (!write_user_config_file())
+    {
+      fprintf(stderr, "\nUnable to write user's config file.\n");
+    }
+  }
+
+  /* Read the lessons directory to determine which lesson   */
+  /* files are available.                                   */
+  if (!parse_lesson_file_directory())
+    fprintf(stderr,"\nCould not parse the lesson file directory.\n");
+
+  /* Now set up high score tables: */
+  initialize_scores();
+  if (!read_high_scores())
+  {
+    fprintf(stderr, "\nCould not find high score table.\n");
+    /* (can still proceed).         */
+  }
+
+#ifdef TUXMATH_DEBUG
+  print_high_scores(stdout);
+#endif
+}
+
+
+
+
+/* Handle any arguments passed from command line */
+void handle_command_args(int argc, char* argv[])
+{
+  DIR *dirp;
+  int i;
+
+  for (i = 1; i < argc; i++)
+  {
+    if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
+    {
+      /* Display help message: */
+
+      printf("\nTux, of Math Command\n\n"
+        "Use the number keys on the keyboard to answer math equations.\n"
+        "If you don't answer a comet's math equation before it hits\n"
+        "one of your cities, the city's shields will be destroyed.\n"
+        "If that city is hit by another comet, it is destroyed completely.\n"
+        "When you lose all of your cities, the game ends.\n\n");
+
+      printf("Note: all settings are now stored in a config file named 'options' in\n"
+             "a hidden directory named './tuxmath' within the user's home directory.\n"
+             "The file consists of simple name/value pairs. It is much easier\n"
+             "to edit this file to set game parameters than to use the command-line\n"
+             "arguments listed below. Also, many options are not selectable from the\n"
+             "command line. The config file contains extensive comments detailing how\n"
+             "to configure the behavior of Tuxmath.\n\n");
+
+      printf("Run the game with:\n"
+        "--homedir dirname      - seek for user home director(ies) in the specified\n"
+        "                         location, rather than the user's actual home\n"
+        "                         directory.  You can set up a user directory tree in\n"
+        "                         this location (see README).  This option is\n"
+        "                         especially useful for schools where all students log\n"
+        "                         in with a single user name.\n"
+        "--optionfile filename  - read config settings from named file. The locations\n"
+        "                         searched for a file with a matching name are the\n"
+        "                         current working directory, the absolute path of the\n"
+        "                         filename, tuxmath's missions directory, the user's\n"
+        "                         tuxmath directory, and the user's home.\n"
+        "--playthroughlist      - to ask each question only once, allowing player to\n"
+        "                         win game if all questions successfully answered\n"
+
+        "--answersfirst   - to ask questions in format: ? + num2 = num3\n"
+        "                   instead of default format: num1 + num2 = ?\n"
+        "--answersmiddle  - to ask questions in format: num1 + ? = num3\n"
+        "                   instead of default format: num1 + num2 = ?\n"
+        "--nosound        - to disable sound/music\n"
+        "--nobackground   - to disable background photos (for slower systems)\n"
+        "--fullscreen     - to run in fullscreen, if possible (vs. windowed)\n"
+        "--windowed       - to run in a window rather than fullscreen\n"
+        "--keypad         - to enable the on-sceen numeric keypad\n"
+        "--demo           - to run the program as a cycling demonstration\n"
+        "--speed S        - set initial speed of the game\n"
+        "                   (S may be fractional, default is 1.0)\n"
+        "--allownegatives - to allow answers to be less than zero\n"
+        );
+
+      printf("\n");
+
+      cleanup_on_error();
+      exit(0);
+    }
+    else if (strcmp(argv[i], "--copyright") == 0 ||
+             strcmp(argv[i], "-c") == 0)
+    {
+      printf(
+        "\n\"Tux, of Math Command\" version " VERSION ", Copyright (C) 2001 Bill Kendrick\n"
+        "This program is free software; you can redistribute it and/or\n"
+        "modify it under the terms of the GNU General Public License\n"
+        "as published by the Free Software Foundation.  See COPYING.txt\n"
+        "\n"
+        "This program is distributed in the hope that it will be useful,\n"
+        "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+        "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
+        "\n");
+
+      cleanup_on_error();
+      exit(0);
+    }
+    else if (strcmp(argv[i], "--usage") == 0 ||
+             strcmp(argv[i], "-u") == 0)
+    {
+      /* Display (happy) usage: */
+
+      usage(0, argv[0]);
+    }
+    else if (0 == strcmp(argv[i], "--homedir"))
+    {
+      // Parse the user choice of a non-default home directory
+      if (i >= argc -1)
+      {
+        fprintf(stderr, "%s option requires an argument (dirname)\n", argv[i]);
+        usage(1, argv[0]);
+      }
+      else // see whether the specified name is a directory
+      {
+        if ((dirp = opendir(argv[i+1])) == NULL)
+          fprintf(stderr,"homedir: %s is not a directory, or it could not be read\n", argv[i+1]);
+        else {
+          set_user_data_dir(argv[i+1]);  // copy the homedir setting
+          closedir(dirp);
+        }
+        i++;   // to pass over the next argument, so remaining options parsed
+      }
+    }
+    else if (0 == strcmp(argv[i], "--optionfile"))
+    {
+      if (i >= argc - 1)
+      {
+        fprintf(stderr, "%s option requires an argument (filename)\n", argv[i]);
+        usage(1, argv[0]);
+      }
+      else /* try to read file named in following arg: */
+      {
+        if (!read_named_config_file(argv[i + 1]))
+        {
+          fprintf(stderr, "Could not read config file: %s\n", argv[i + 1]);
+        }
+      }
+      i++; /* so program doesn't barf on next arg (the filename) */
+    }
+    else if (strcmp(argv[i], "--fullscreen") == 0 ||
+             strcmp(argv[i], "-f") == 0)
+    {
+      Opts_SetGlobalOpt(FULLSCREEN, 1);
+    }
+    else if (strcmp(argv[i], "--windowed") == 0 ||
+             strcmp(argv[i], "-w") == 0)
+    {
+      Opts_SetGlobalOpt(FULLSCREEN, 0);
+    }
+    else if (strcmp(argv[i], "--nosound") == 0 ||
+             strcmp(argv[i], "-s") == 0 ||
+             strcmp(argv[i], "--quiet") == 0 ||
+             strcmp(argv[i], "-q") == 0)
+    {
+      Opts_SetGlobalOpt(USE_SOUND, -1);  // prevent options files from overwriting
+    }
+    else if (strcmp(argv[i], "--version") == 0 ||
+             strcmp(argv[i], "-v") == 0)
+    {
+      printf("Tux, of Math Command (\"tuxmath\")\n"
+             "Version " VERSION "\n");
+      cleanup_on_error();
+      exit(0);
+    }
+    else if (strcmp(argv[i], "--nobackground") == 0 ||
+             strcmp(argv[i], "-b") == 0)
+    {
+      Opts_SetUseBkgd(0);
+    }
+    else if (strcmp(argv[i], "--demo") == 0 ||
+             strcmp(argv[i], "-d") == 0)
+    {
+      Opts_SetDemoMode(1);
+    }
+    else if (strcmp(argv[i], "--keypad") == 0 ||
+             strcmp(argv[i], "-k") == 0)
+    {
+      Opts_SetGlobalOpt(USE_KEYPAD, 1);
+    }
+    else if (strcmp(argv[i], "--allownegatives") == 0 ||
+             strcmp(argv[i], "-n") == 0)
+    {
+      MC_SetOpt(ALLOW_NEGATIVES, 1);
+    }
+    else if (strcmp(argv[i], "--playthroughlist") == 0 ||
+             strcmp(argv[i], "-l") == 0)
+    {
+      MC_SetOpt(PLAY_THROUGH_LIST, 1);
+    }
+    else if (strcmp(argv[i], "--answersfirst") == 0)
+    {
+      MC_SetOpt(FORMAT_ANSWER_LAST, 0);
+      MC_SetOpt(FORMAT_ANSWER_FIRST, 1);
+      MC_SetOpt(FORMAT_ANSWER_MIDDLE, 0);
+    }
+    else if (strcmp(argv[i], "--answersmiddle") == 0)
+    {
+      MC_SetOpt(FORMAT_ANSWER_LAST, 0);
+      MC_SetOpt(FORMAT_ANSWER_FIRST, 0);
+      MC_SetOpt(FORMAT_ANSWER_MIDDLE, 1);
+    }
+    else if (strcmp(argv[i], "--speed") == 0 ||
+             strcmp(argv[i], "-s") == 0)
+    {
+      if (i >= argc - 1)
+      {
+        fprintf(stderr, "%s option requires an argument\n", argv[i]);
+        usage(1, argv[0]);
+      }
+
+      Opts_SetSpeed(strtod(argv[i + 1], (char **) NULL));
+      i++;
+    }
+
+    else
+    /* TODO try to match unrecognized strings to config file names */
+    {
+      /* Display 'made' usage: */
+
+      fprintf(stderr, "Unknown option: %s\n", argv[i]);
+      usage(1, argv[0]);
+    }
+  }/* end of command-line args */
+
+
+  if (Opts_DemoMode() && Opts_GetGlobalOpt(USE_KEYPAD))
+  {
+    fprintf(stderr, "No use for keypad in demo mode!\n");
+    Opts_SetGlobalOpt(USE_KEYPAD, 0);
+  }
+}
+
+void initialize_SDL(void)
+{
+  // Audio parameters
+  int frequency,channels,n_timesopened;
+  Uint16 format;
+
+  /* Init SDL Video: */
+  screen = NULL;
+
+  if (SDL_Init(SDL_INIT_VIDEO) < 0)
+  {
+    fprintf(stderr,
+           "\nError: I could not initialize video!\n"
+           "The Simple DirectMedia error that occured was:\n"
+           "%s\n\n", SDL_GetError());
+    cleanup_on_error();
+    exit(1);
+  }
+
+
+  if (TTF_Init() < 0)
+  {
+    fprintf( stderr, "Couldn't initialize SDL_ttf\n"
+           "The Simple DirectMedia error that occured was:\n"
+           "%s\n\n", SDL_GetError());
+    cleanup_on_error();
+    exit(2);
+  }
+
+  atexit(TTF_Quit); // Maybe this is redundant?
+
+#ifdef HAVE_LIBSDL_PANGO
+  if (SDLPango_Init () < 0)
+  {
+      fprintf(stderr,
+            "\nWarning: I could not initialize SDL_Pango !\n"
+            "%s\n\n", SDL_GetError());
+  }
+  else {
+    init_SDLPango_Context();
+  }
+#endif
+
+
+  #ifndef NOSOUND
+  /* Init SDL Audio: */
+  Opts_SetSoundHWAvailable(0);  // By default no sound HW
+  if (Opts_GetGlobalOpt(USE_SOUND))
+  {
+    if (SDL_Init(SDL_INIT_AUDIO) < 0)
+    {
+      fprintf(stderr,
+            "\nWarning: I could not initialize audio!\n"
+            "The Simple DirectMedia error that occured was:\n"
+            "%s\n\n", SDL_GetError());
+    }
+    else {
+      //if (Mix_OpenAudio(44100, AUDIO_S16SYS, 2, 2048) < 0)
+      if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, AUDIO_S16SYS, 2, 2048) < 0)
+      {
+        fprintf(stderr,
+                "\nWarning: I could not set up audio for 44100 Hz "
+                "16-bit stereo.\n"
+                "The Simple DirectMedia error that occured was:\n"
+                "%s\n\n", SDL_GetError());
+
+      }
+    }
+    n_timesopened = Mix_QuerySpec(&frequency,&format,&channels);
+    if (n_timesopened > 0)
+      Opts_SetSoundHWAvailable(1);
+    else
+      frequency = format = channels = 0; //more helpful than garbage
+    tmdprintf("Sound mixer: frequency = %d, "
+                    "format = %x, "
+                    "channels = %d, "
+                    "n_timesopened = %d\n",
+                    frequency,format,channels,n_timesopened);
+  }
+
+  #endif
+  {
+    const SDL_VideoInfo *videoInfo;
+    Uint32 surfaceMode;
+    videoInfo = SDL_GetVideoInfo();
+    if (videoInfo->hw_available)
+    {
+      surfaceMode = SDL_HWSURFACE;
+      tmdprintf("HW mode\n");
+    }
+    else
+    {
+      surfaceMode = SDL_SWSURFACE;
+      tmdprintf("SW mode\n");
+    }
+
+    // Determine the current resolution: this will be used as the
+    // fullscreen resolution, if the user wants fullscreen.
+    tmdprintf("Current resolution: w %d, h %d.\n",videoInfo->current_w,videoInfo->current_h);
+    fs_res_x = videoInfo->current_w;
+    fs_res_y = videoInfo->current_h;
+
+    if (Opts_GetGlobalOpt(FULLSCREEN))
+    {
+      screen = SDL_SetVideoMode(fs_res_x, fs_res_y, PIXEL_BITS, SDL_FULLSCREEN | surfaceMode);
+      if (screen == NULL)
+      {
+        fprintf(stderr,
+              "\nWarning: I could not open the display in fullscreen mode.\n"
+              "The Simple DirectMedia error that occured was:\n"
+              "%s\n\n", SDL_GetError());
+        Opts_SetGlobalOpt(FULLSCREEN, 0);
+      }
+    }
+
+    if (!Opts_GetGlobalOpt(FULLSCREEN))
+    {
+      screen = SDL_SetVideoMode(RES_X, RES_Y, PIXEL_BITS, surfaceMode);
+    }
+
+    if (screen == NULL)
+    {
+      fprintf(stderr,
+            "\nError: I could not open the display.\n"
+            "The Simple DirectMedia error that occured was:\n"
+            "%s\n\n", SDL_GetError());
+      cleanup_on_error();
+      exit(1);
+    }
+
+    seticon();
+
+    SDL_WM_SetCaption("Tux, of Math Command", "TuxMath");
+  }
+
+  /* --- Define the colors we use --- */
+  /* This was moved here because of wanting to replace some images
+     with fonts during setup */
+  black.r       = 0x00; black.g       = 0x00; black.b       = 0x00;
+  gray.r        = 0x80; gray.g        = 0x80; gray.b        = 0x80;
+  dark_blue.r   = 0x00; dark_blue.g   = 0x00; dark_blue.b   = 0x60;
+  red.r         = 0xff; red.g         = 0x00; red.b         = 0x00;
+  white.r       = 0xff; white.g       = 0xff; white.b       = 0xff;
+  yellow.r      = 0xff; yellow.g      = 0xff; yellow.b      = 0x00;
+}
+
+
+void load_data_files(void)
+{
+  if (!load_sound_data())
+  {
+    fprintf(stderr, "\nCould not load sound file - attempting to proceed without sound.\n");
+    Opts_SetSoundHWAvailable(0);
+  }
+
+   if (!load_default_font())
+  {
+    fprintf(stderr, "\nCould not load default font - exiting!\n");
+    cleanup_on_error();
+    exit(1);
+  }
+   
+  /* This now has to come after loading the font, because it replaces
+     a couple of images with translatable versions. */
+  if (!load_image_data())
+  {
+    fprintf(stderr, "\nCould not load image file - exiting!\n");
+    cleanup_on_error();
+    exit(1);
+  }
+}
+
+
+
+/* Create flipped versions of certain images; also set up the flip
+   lookup table */
+void generate_flipped_images(void)
+{
+  int i;
+
+  /* Zero out the flip lookup table */
+  for (i = 0; i < NUM_IMAGES; i++)
+    flipped_img_lookup[i] = 0;
+
+  for (i = 0; i < NUM_FLIPPED_IMAGES; i++) {
+    flipped_images[i] = Flip(images[flipped_img[i]],1,0);
+    flipped_img_lookup[flipped_img[i]] = i;
+  }
+}
+
+/* Created images that are blends of two other images to smooth out
+   the transitions. */
+void generate_blended_images(void)
+{
+  blended_igloos[0] = Blend(images[IMG_IGLOO_REBUILDING1],NULL,0.06);
+  blended_igloos[1] = Blend(images[IMG_IGLOO_REBUILDING1],NULL,0.125);
+  blended_igloos[2] = Blend(images[IMG_IGLOO_REBUILDING1],NULL,0.185);
+  blended_igloos[3] = Blend(images[IMG_IGLOO_REBUILDING1],NULL,0.25);
+  blended_igloos[4] = Blend(images[IMG_IGLOO_REBUILDING1],NULL,0.5);
+  blended_igloos[5] = Blend(images[IMG_IGLOO_REBUILDING1],NULL,0.75);
+  blended_igloos[6] = images[IMG_IGLOO_REBUILDING1];
+  blended_igloos[7] = Blend(images[IMG_IGLOO_REBUILDING2],images[IMG_IGLOO_REBUILDING1],0.25);
+  blended_igloos[8] = Blend(images[IMG_IGLOO_REBUILDING2],images[IMG_IGLOO_REBUILDING1],0.5);
+  blended_igloos[9] = Blend(images[IMG_IGLOO_REBUILDING2],images[IMG_IGLOO_REBUILDING1],0.75);
+  blended_igloos[10] = images[IMG_IGLOO_REBUILDING2];
+  blended_igloos[11] = Blend(images[IMG_IGLOO_INTACT],images[IMG_IGLOO_REBUILDING2],0.25);
+  blended_igloos[12] = Blend(images[IMG_IGLOO_INTACT],images[IMG_IGLOO_REBUILDING2],0.5);
+  blended_igloos[13] = Blend(images[IMG_IGLOO_INTACT],images[IMG_IGLOO_REBUILDING2],0.75);
+  blended_igloos[14] = images[IMG_IGLOO_INTACT];
+}
+
+
+/* save options and free heap */
+/* use for successful exit */
+void cleanup(void)
+{
+  /* No longer write settings here, because we only */
+  /* want to save settings from certain types of games. */
+  //write_user_config_file();
+  cleanup_memory();
+  exit(0);
+}
+
+
+
+/* save options and free heap */
+/* use for fail exit */
+void cleanup_on_error(void)
+{
+  cleanup_memory();
+  exit(1);
+}
+
+
+
+/* free any heap memory used during game DSB */
+/* and also quit SDL properly:               */
+/* NOTE - this function will get called twice if   */
+/* exit occurs because of window close, so we      */
+/* need to check all pointers before freeing them, */
+/* and set them to NULL after freeing them, so we  */
+/* avoid segfaults at exit from double free()      */
+void cleanup_memory(void)
+{
+  int i;
+  int frequency,channels,n_timesopened;
+  Uint16 format;
+
+  /* Free memory used for line breaking */
+#ifdef LINEBREAK
+  linewrap_cleanup();
+#endif
+
+  /* Free all images and sounds used by SDL: */
+  if(default_font)
+  {
+    TTF_CloseFont(default_font);
+    default_font = NULL;
+    TTF_Quit();
+  }
+
+  for (i = 0; i < NUM_IMAGES; i++)
+  {
+    if (images[i])
+      SDL_FreeSurface(images[i]);
+    images[i] = NULL;
+  }
+
+  for (i = 0; i < NUM_SOUNDS; i++)
+  {
+    if (sounds[i])
+      Mix_FreeChunk(sounds[i]);
+    sounds[i] = NULL;
+  }
+
+  for (i = 0; i < NUM_MUSICS; i++)
+  {
+    if (musics[i])
+      Mix_FreeMusic(musics[i]);
+    musics[i] = NULL;
+  }
+
+  if (lesson_list_titles)
+  {
+    for (i = 0; i < num_lessons; i++)
+    {
+      if (lesson_list_titles[i])
+      {
+        free(lesson_list_titles[i]);
+        lesson_list_titles[i] = NULL;
+      }
+    }
+    free(lesson_list_titles);
+    lesson_list_titles = NULL;
+  }
+
+  if (lesson_list_filenames)
+  {
+    for (i = 0; i < num_lessons; i++)
+    {
+      if (lesson_list_filenames[i])
+      {
+        free(lesson_list_filenames[i]);
+        lesson_list_filenames[i] = NULL;
+      }
+    }
+    free(lesson_list_filenames);
+    lesson_list_filenames = NULL;
+  }
+
+  if (lesson_list_goldstars)
+  {
+    free(lesson_list_goldstars);
+    lesson_list_goldstars = NULL;
+  }
+
+  // Close the audio mixer. We have to do this at least as many times
+  // as it was opened.
+  n_timesopened = Mix_QuerySpec(&frequency,&format,&channels);
+  while (n_timesopened) {
+    Mix_CloseAudio();
+    n_timesopened--;
+  }
+
+#ifdef HAVE_LIBSDL_PANGO
+   free_SDLPango_Context();
+#endif
+
+
+  // Finally, quit SDL
+  SDL_Quit();
+
+  /* frees the game_options struct: */
+  Opts_Cleanup();
+  /* frees any heap used by MathCards: */
+  MC_EndGame();
+}
+
+
+
+/* Set the application's icon: */
+
+void seticon(void)
+{
+  int masklen;
+  Uint8* mask;
+  SDL_Surface* icon;
+
+
+  /* Load icon into a surface: */
+  icon = IMG_Load(DATA_PREFIX "/images/icons/icon.png");
+  if (icon == NULL)
+  {
+    fprintf(stderr,
+            "\nWarning: I could not load the icon image: %s\n"
+            "The Simple DirectMedia error that occured was:\n"
+            "%s\n\n", DATA_PREFIX "/images/icons/icon.png", SDL_GetError());
+    return;
+  }
+
+
+  /* Create mask: */
+  masklen = (((icon -> w) + 7) / 8) * (icon -> h);
+  mask = malloc(masklen * sizeof(Uint8));
+  memset(mask, 0xFF, masklen);
+
+
+  /* Set icon: */
+  SDL_WM_SetIcon(icon, mask);
+
+
+  /* Free icon surface & mask: */
+  free(mask);
+  SDL_FreeSurface(icon);
+
+
+  /* Seed random-number generator: */
+  srand(SDL_GetTicks());
+}
+
+void usage(int err, char * cmd)
+{
+  FILE * f;
+
+  if (err == 0)
+    f = stdout;
+  else
+    f = stderr;
+
+  fprintf(f,
+   "\nUsage: %s {--help | --usage | --copyright}\n"
+   "          [--optionfile <filename>]\n"
+   "          [--playthroughlist] [--answersfirst] [--answersmiddle]\n"
+   "       %s [--fullscreen] [--nosound] [--nobackground]\n"
+   "          [--demo] [--keypad] [--allownegatives]\n"
+//   "          [--operator {add | subtract | multiply | divide} ...]\n"
+   "          [--speed <val>]\n"
+    "\n", cmd, cmd);
+
+  exit (err);
+}
+

Added: tuxmath/trunk/src/setup.h
===================================================================
--- tuxmath/trunk/src/setup.h	                        (rev 0)
+++ tuxmath/trunk/src/setup.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,38 @@
+/*
+  setup.h
+
+  For TuxMath
+  Contains functions to initialize the settings structs, 
+  read in command-line arguments, and to clean up on exit.
+  All code involving file I/O has been moved to fileops.h/fileops.c
+  and is called from the main setup function.
+
+  Some globals are declared in setup.c - all globals throughout tuxmath
+  are now extern'd in the same place in tuxmath.h
+
+  by Bill Kendrick
+  bill at newbreedsoftware.com
+  http://www.newbreedsoftware.com/
+
+
+  Part of "Tux4Kids" Project
+  http://www.tux4kids.org/
+      
+  August 26, 2001 - February 18, 2004
+
+  Modified by David Bruce
+  dbruce at tampabay.rr.com
+  September 1, 2006
+*/
+
+
+#ifndef SETUP_H
+#define SETUP_H
+
+
+void setup(int argc, char * argv[]);
+void cleanup(void);
+void cleanup_on_error(void);
+extern void initialize_options_user(void);
+
+#endif

Added: tuxmath/trunk/src/svn-commit.tmp
===================================================================
--- tuxmath/trunk/src/svn-commit.tmp	                        (rev 0)
+++ tuxmath/trunk/src/svn-commit.tmp	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,36 @@
+updated source files
+
+--This line, and those below, will be ignored--
+
+A    Makefile.in
+M    mathcards.c
+D    localcharset.h
+D    cjk.h
+M    campaign.c
+D    unitypes.h
+M    loaders.h
+D    uniwidth.h
+D    u16-mbtouc-aux.c
+D    u16-mbtouc.c
+M    SDL_extras.c
+AM   exercise_mathcards
+D    u16-mbtouc-unsafe-aux.c
+D    width.c
+M    titlescreen.c
+D    u16-mbtouc-unsafe.c
+M    lessons.c
+D    linebreak.c
+D    u8-mbtouc-unsafe-aux.c
+D    lbrkprop.h
+D    linebreak.h
+D    u8-mbtouc-unsafe.c
+D    streq.h
+D    xsize.h
+D    u8-mbtouc-aux.c
+D    u8-uctomb-aux.c
+M    linewrap.c
+D    u8-mbtouc.c
+D    u8-uctomb.c
+M    linewrap.h
+M    Makefile.am
+D    unistr.h

Added: tuxmath/trunk/src/titlescreen.c
===================================================================
--- tuxmath/trunk/src/titlescreen.c	                        (rev 0)
+++ tuxmath/trunk/src/titlescreen.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,2617 @@
+/***************************************************************************
+ -  file: titlescreen.c
+ -  description: splash, title and menu screen functionality
+                            ------------------
+    begin                : Thur May 4 2000
+    copyright            : (C) 2000 by Sam Hart
+                         : (C) 2003 by Jesse Andrews
+    email                : tuxtype-dev at tux4kids.net
+
+    Modified for use in tuxmath by David Bruce - 2006-2007.
+    email                : <dbruce at tampabay.rr.com>
+                           <tuxmath-devel at lists.sourceforge.net>
+    Also significantly enhanced by Tim Holy - 2007
+***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+
+// titlescreen.h has all of the tuxtype-related stuff:
+#include "titlescreen.h"
+
+// tuxmath includes:
+#include "tuxmath.h"
+#include "options.h"
+#include "fileops.h"
+#include "game.h"
+#include "campaign.h"
+#include "factoroids.h"
+#include "multiplayer.h"
+#include "mathcards.h"
+#include "setup.h"     //for cleanup()
+#include "loaders.h"
+#include "credits.h"
+#include "highscore.h"
+#include "convert_utf.h" // for wide char to UTF-8 conversion
+#include "SDL_extras.h"
+
+/* --- Data Structure for Dirty Blitting --- */
+SDL_Rect srcupdate[MAX_UPDATES];
+SDL_Rect dstupdate[MAX_UPDATES];
+int numupdates = 0; // tracks how many blits to be done
+
+// Colors we use:
+SDL_Color black;
+SDL_Color gray;
+SDL_Color dark_blue;
+SDL_Color red;
+SDL_Color white;
+SDL_Color yellow;
+
+// Type needed for TransWipe():
+struct blit {
+    SDL_Surface *src;
+    SDL_Rect *srcrect;
+    SDL_Rect *dstrect;
+    unsigned char type;
+} blits[MAX_UPDATES];
+
+// Lessons available for play
+char **lesson_list_titles = NULL;
+char **lesson_list_filenames = NULL;
+int num_lessons = 0;
+
+
+/* --- media for menus --- */
+
+enum {
+  SPRITE_TRAINING,
+  SPRITE_ARCADE,
+  SPRITE_HELP,
+  SPRITE_CUSTOM,
+  SPRITE_OPTIONS,
+  SPRITE_CADET,
+  SPRITE_SCOUT,
+  SPRITE_RANGER,
+  SPRITE_ACE,
+  SPRITE_COMMANDO,
+  SPRITE_QUIT,
+  SPRITE_MAIN,
+  SPRITE_GOLDSTAR,
+  SPRITE_NO_GOLDSTAR,
+  SPRITE_TROPHY,
+  SPRITE_CREDITS,
+  SPRITE_ALONE,
+  SPRITE_FRIENDS,
+  SPRITE_FACTOROIDS,
+  SPRITE_FACTORS,
+  SPRITE_FRACTIONS,
+  SPRITE_CAMPAIGN,
+  SPRITE_SSWEEP,
+  SPRITE_ELIMINATION,
+  N_SPRITES};
+
+const char* menu_sprite_files[N_SPRITES] =
+{
+  "lesson",
+  "comet",
+  "help",
+  "tux_config",
+  "tux_config_brown",
+  "tux_helmet_yellow",
+  "tux_helmet_green",
+  "tux_helmet_blue",
+  "tux_helmet_red",
+  "tux_helmet_black",
+  "quit",
+  "main",
+  "goldstar",
+  "no_goldstar",
+  "trophy",
+  "credits",
+  "alone", 
+  "friends", 
+  "factoroids",
+  "factors",
+  "fractions",
+  "fleet",
+  "nums",
+  "exclamation"
+};
+
+sprite **sprite_list = NULL;
+
+sprite* Tux = NULL;
+
+
+SDL_Event event;
+
+/* --- locations we need --- */
+
+SDL_Rect dest,
+         Tuxdest,
+         Titledest,
+         stopRect,
+         Backrect,
+         Tuxback,
+         Titleback,
+         cursor,
+         beak;
+
+/* The background image scaled to windowed 640x480 */
+SDL_Surface* bkg = NULL;
+/* The background image scaled to fullscreen dimensions */
+SDL_Surface* scaled_bkg = NULL;
+/* "Easter Egg" cursor */
+SDL_Surface* egg = NULL;
+int egg_active = 0; //are we currently using the egg cursor?
+
+
+SDL_Surface* current_bkg()
+  /* This syntax makes my brain start to explode! */
+  { return screen->flags & SDL_FULLSCREEN ? scaled_bkg : bkg; }
+
+/* Local function prototypes: */
+void TitleScreen_load_menu(void);
+void TitleScreen_unload_menu(void);
+int TitleScreen_load_media(void);
+void TitleScreen_unload_media(void);
+void NotImplemented(void);
+void TransWipe(SDL_Surface* newbkg, int type, int var1, int var2);
+void UpdateScreen(int* frame);
+void AddRect(SDL_Rect* src, SDL_Rect* dst);
+void InitEngine(void);
+void ShowMessage(const char* str1, const char* str2, const char* str3, const char* str4);
+void RecalcTitlePositions();
+void RecalcMenuPositions(int*, int, menu_options*, void (*)(menu_options*),
+                         SDL_Rect**, SDL_Rect**, SDL_Rect**,
+                         SDL_Rect**, SDL_Rect**, SDL_Rect**,
+                         SDL_Rect*, SDL_Rect*);
+void set_buttons_max_width(SDL_Rect *, SDL_Rect *, int);
+
+int run_login_menu(void);
+int run_main_menu(void);
+int run_game_menu(void);
+int run_multiplay_menu(void);
+int run_lessons_menu(void);
+int run_arcade_menu(void);
+int run_campaign_menu(void);
+int run_custom_menu(void);
+int run_activities_menu(void);
+int run_options_menu(void);
+int handle_easter_egg(const SDL_Event* evt);
+
+
+
+/***********************************************************/
+/*                                                         */
+/*       "Public functions" (callable throughout program)  */
+/*                                                         */
+/***********************************************************/
+
+
+
+/****************************************
+* TitleScreen: Display the title screen *
+****************************************/
+
+/* display title screen, get input */
+
+void TitleScreen(void)
+{
+
+  Uint32 start = 0;
+
+  int i,TuxPixSkip,TitlePixSkip;
+//  int n_subdirs;
+//  char **subdir_names;
+
+
+  if (Opts_UsingSound())
+  {
+    Opts_SetGlobalOpt(MENU_SOUND, 1);
+    Opts_SetGlobalOpt(MENU_MUSIC, 1);
+//    menu_music = localsettings.menu_music;
+  }
+
+  InitEngine();  //set up pointers for blitting structure.
+
+  start = SDL_GetTicks();
+
+
+  /* StandbyScreen: Display the Standby screen: */
+  if (images[IMG_STANDBY])
+  {
+    // Center horizontally
+    dest.x = ((screen->w) / 2) - (images[IMG_STANDBY]->w) / 2;
+    // Center vertically
+    dest.y = ((screen->h) / 2) - (images[IMG_STANDBY]->h) / 2;
+    dest.w = images[IMG_STANDBY]->w;
+    dest.h = images[IMG_STANDBY]->h;
+
+    SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));
+    SDL_BlitSurface(images[IMG_STANDBY], NULL, screen, &dest);
+    SDL_UpdateRect(screen, 0, 0, 0, 0);
+    // Play "harp" greeting sound lifted from Tux Paint:
+    playsound(SND_HARP);
+ }
+
+
+  /* --- wait  --- */
+
+  while ((SDL_GetTicks() - start) < 2000)
+  {
+    /* Check to see if user pressed escape */
+    if (SDL_PollEvent(&event)
+     && event.type==SDL_KEYDOWN
+     && event.key.keysym.sym == SDLK_ESCAPE)
+    {
+      return;
+    }
+    SDL_Delay(50);
+  }
+#ifndef TUXMATH_DEBUG //in case of a freeze, this traps the cursor
+  SDL_WM_GrabInput(SDL_GRAB_ON); // User input goes to TuxMath, not window manager
+#endif
+  SDL_ShowCursor(1);
+
+
+  /***************************
+  * Tux and Title animations *
+  ***************************/
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "->Now Animating Tux and Title onto the screen\n" );
+#endif
+
+  /* Load media and menu data: */
+  /* FIXME should get out if needed media not loaded OK */
+  if (TitleScreen_load_media() == 0) {
+    fprintf(stderr,"Media was not properly loaded, exiting");
+    return;
+  }
+
+  /* Draw background, if it loaded OK: */
+  if (current_bkg() )
+  {
+    Backrect.x = (screen->w - current_bkg()->w) / 2;
+    Backrect.y = (screen->h - current_bkg()->h) / 2;
+    Backrect.w = current_bkg()->w;
+    Backrect.h = current_bkg()->h;
+    /* FIXME not sure TransWipe() works in Windows: */
+    TransWipe(current_bkg(), RANDOM_WIPE, 10, 20);
+    /* Make sure background gets drawn (since TransWipe() doesn't */
+    /* seem to work reliably as of yet):                          */
+    SDL_BlitSurface(current_bkg(), NULL, screen, &Backrect);
+
+  }
+  /* Red "Stop" circle in upper right corner to go back to main menu: */
+  if (images[IMG_STOP])
+  {
+    stopRect.w = images[IMG_STOP]->w;
+    stopRect.h = images[IMG_STOP]->h;
+    stopRect.x = screen->w - images[IMG_STOP]->w;
+    stopRect.y = 0;
+    SDL_BlitSurface(images[IMG_STOP], NULL, screen, &stopRect);
+  }
+  SDL_UpdateRect(screen, 0, 0, 0, 0);
+
+  /* --- Pull tux & logo onscreen --- */
+  /* NOTE we wind up with Tuxdest.y == (screen->h)  - (Tux->frame[0]->h), */
+  /* a 	nd Titledest.x == 0.                                                */
+  if (current_bkg()
+   && images[IMG_MENU_TITLE]
+   && images[IMG_STOP]
+   && Tux && Tux->frame[0])
+  {
+
+    Tuxdest.x = 0;
+    Tuxdest.y = screen->h;
+    /*
+    Tuxback.x = Tuxdest.x - Backrect.x;
+    Tuxback.y = Tuxdest.y - Backrect.y;
+    */
+    Tuxdest.w = Tuxback.w = Tux->frame[0]->w;
+    Tuxdest.h = Tuxback.h = Tux->frame[0]->h;
+
+
+    Titledest.x = screen->w;
+    Titledest.y = 10;
+    /*
+    Titleback.x = Titledest.x - Backrect.x;
+    Titleback.y = Titledest.y - Backrect.y;
+    */
+    Titledest.w = Titleback.w = images[IMG_MENU_TITLE]->w;
+    Titledest.h = Titleback.h = images[IMG_MENU_TITLE]->h;
+
+    TuxPixSkip = Tux->frame[0]->h / (PRE_ANIM_FRAMES * PRE_FRAME_MULT);
+    TitlePixSkip = (screen->w) / (PRE_ANIM_FRAMES * PRE_FRAME_MULT);
+
+    for (i = 0; i < (PRE_ANIM_FRAMES * PRE_FRAME_MULT); i++)
+    {
+      start = SDL_GetTicks();
+
+      //Draw the entire background, over a black screen if necessary
+      if (current_bkg()->w != screen->w || current_bkg()->h != screen->h)
+        SDL_FillRect(screen, &screen->clip_rect, 0);
+      SDL_BlitSurface(current_bkg(), NULL, screen, &Backrect);
+
+      Tuxdest.y -= TuxPixSkip;
+      //Tuxback.y -= Tux->frame[0]->h / (PRE_ANIM_FRAMES * PRE_FRAME_MULT);
+      Titledest.x -= TitlePixSkip;
+      //Titleback.y -= (screen->w) / (PRE_ANIM_FRAMES * PRE_FRAME_MULT);
+
+      SDL_BlitSurface(Tux->frame[0], NULL, screen, &Tuxdest);
+      SDL_BlitSurface(images[IMG_MENU_TITLE], NULL, screen, &Titledest);
+      SDL_BlitSurface(images[IMG_STOP], NULL, screen, &stopRect);
+
+      SDL_UpdateRect(screen, Tuxdest.x, Tuxdest.y, Tuxdest.w, Tuxdest.h);
+      SDL_UpdateRect(screen, Titledest.x, Titledest.y, Titledest.w + TitlePixSkip, Titledest.h);
+      SDL_UpdateRect(screen, stopRect.x, stopRect.y, stopRect.w, stopRect.h);
+
+      while ((SDL_GetTicks() - start) < 33)
+      {
+        SDL_Delay(2);
+      }
+    }
+
+
+  }
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "Tux and Title are in place now\n");
+#endif
+
+  //location of Tux's beak
+  beak.x = Tuxdest.x + 70;
+  beak.y = Tuxdest.y + 60;
+  beak.w = beak.h = 50;
+
+  /* Start playing menu music if desired: */
+  if (Opts_GetGlobalOpt(MENU_MUSIC))
+  {
+    audioMusicLoad("tuxi.ogg", -1);
+  }
+
+  /* If necessary, have the user log in */
+  if (run_login_menu() != -1) {
+    /* Finish parsing user options */
+    initialize_options_user();
+    /* Start the main menu */
+    run_main_menu();
+  }
+
+  /* User has selected quit, clean up */
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "->>Freeing title screen images\n");
+#endif
+
+  TitleScreen_unload_media();
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr,"->TitleScreen():END \n");
+#endif
+
+}
+
+
+
+
+/***********************************************************/
+/*                                                         */
+/*    "Private functions" (callable only from this file)   */
+/*                                                         */
+/***********************************************************/
+
+
+// 1 = success, 0 = failure
+int TitleScreen_load_media(void)
+{
+  char fn[PATH_MAX];
+  int i;
+
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "Entering TitleScreen_load_media():\n");
+#endif
+
+  Tux = LoadSprite("tux/bigtux", IMG_ALPHA);
+
+  SDL_ShowCursor(1);
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "loading sprites\n");
+#endif
+
+  sprite_list = (sprite**) malloc(N_SPRITES*sizeof(sprite*));
+  if (sprite_list == NULL)
+    return 0;
+
+  for (i = 0; i < N_SPRITES; i++) {
+    /* --- load animated icon for menu item --- */
+    sprintf(fn, "sprites/%s", menu_sprite_files[i]);
+    sprite_list[i] = LoadSprite(fn, IMG_ALPHA);
+  }
+  egg = LoadImage("title/egg.png",
+                  IMG_COLORKEY | IMG_NOT_REQUIRED);
+  LoadBothBkgds("title/menu_bkg.jpg", &scaled_bkg, &bkg);
+  return 1;
+}
+
+
+
+
+void TitleScreen_unload_menu(void)
+{
+  int i;
+
+  for (i = 0; i < N_SPRITES; i++)
+  {
+    tmdprintf("Freeing image #%d: ", i);
+    FreeSprite(sprite_list[i]);
+  }
+  free(sprite_list);
+  tmdprintf("Images freed\n");
+  sprite_list = NULL;
+}
+
+
+
+void TitleScreen_unload_media(void)
+{
+  tmdprintf("Unloading media\n");
+  FreeSprite(Tux);
+  Tux = NULL;
+  TitleScreen_unload_menu();
+
+  SDL_FreeSurface(egg);
+  SDL_FreeSurface(bkg);
+  SDL_FreeSurface(scaled_bkg);
+}
+
+
+
+void NotImplemented(void)
+{
+  const char *s1, *s2, *s3, *s4;
+
+  s1 = _("Work In Progress!");
+  s2 = _("This feature is not ready yet");
+  s3 = _("Discuss the future of TuxMath at");
+  s4 = N_("tuxmath-devel at lists.sourceforge.net");
+
+  ShowMessage(s1, s2, s3, s4);
+}
+
+
+
+
+
+/* FIXME add some background shading to improve legibility */
+void ShowMessage(const char* str1, const char* str2, const char* str3, const char* str4)
+{
+  SDL_Surface *s1, *s2, *s3, *s4;
+  SDL_Rect loc;
+  int finished = 0;
+  int tux_frame = 0;
+  Uint32 frame = 0;
+  Uint32 start = 0;
+
+  s1 = s2 = s3 = s4 = NULL;
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "ShowMessage() - creating text\n" );
+#endif
+
+  if (str1)
+    s1 = BlackOutline(str1, default_font, &white);
+  if (str2)
+    s2 = BlackOutline(str2, default_font, &white);
+  if (str3)
+    s3 = BlackOutline(str3, default_font, &white);
+  /* When we get going with i18n may need to modify following - see below: */
+  if (str4)
+    s4 = BlackOutline(str4, default_font, &white);
+
+//   /* we always want the URL in english */
+//   if (!useEnglish)
+//   {
+//     TTF_Font *english_font;
+//     useEnglish = 1;
+//     english_font = LoadFont( menu_font, menu_font_size );
+//     s4 = black_outline( "tuxmath-devel at lists.sourceforge.net", english_font, &white);
+//     TTF_CloseFont(english_font);
+//     useEnglish = 0;
+//   }
+//   else
+//   {
+//     s4 = black_outline( "tuxmath-devel at lists.sourceforge.net", default_font, &white);
+//   }
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "NotImplemented() - drawing screen\n" );
+#endif
+
+  /* Redraw background: */
+  if (current_bkg() )
+    SDL_BlitSurface( current_bkg(), NULL, screen, &Backrect );
+
+  /* Red "Stop" circle in upper right corner to go back to main menu: */
+  if (images[IMG_STOP])
+  {
+    stopRect.w = images[IMG_STOP]->w;
+    stopRect.h = images[IMG_STOP]->h;
+    stopRect.x = screen->w - images[IMG_STOP]->w;
+    stopRect.y = 0;
+    SDL_BlitSurface(images[IMG_STOP], NULL, screen, &stopRect);
+  }
+
+  if (Tux && Tux->num_frames) /* make sure sprite has at least one frame */
+  {
+    SDL_BlitSurface(Tux->frame[0], NULL, screen, &Tuxdest);
+  }
+
+  /* Draw lines of text (do after drawing Tux so text is in front): */
+  if (s1)
+  {
+    loc.x = (screen->w / 2) - (s1->w/2); loc.y = 10;
+    SDL_BlitSurface( s1, NULL, screen, &loc);
+  }
+  if (s2)
+  {
+    loc.x = (screen->w / 2) - (s2->w/2); loc.y = 60;
+    SDL_BlitSurface( s2, NULL, screen, &loc);
+  }
+  if (s3)
+  {
+    //loc.x = 320 - (s3->w/2); loc.y = 300;
+    loc.x = (screen->w / 2) - (s3->w/2); loc.y = 110;
+    SDL_BlitSurface( s3, NULL, screen, &loc);
+  }
+  if (s4)
+  {
+    //loc.x = 320 - (s4->w/2); loc.y = 340;
+    loc.x = (screen->w / 2) - (s4->w/2); loc.y = 200;
+    SDL_BlitSurface( s4, NULL, screen, &loc);
+  }
+
+  /* and update: */
+  SDL_UpdateRect(screen, 0, 0, 0, 0);
+
+  while (!finished)
+  {
+    start = SDL_GetTicks();
+
+    while (SDL_PollEvent(&event))
+    {
+      switch (event.type)
+      {
+        case SDL_QUIT:
+        {
+          cleanup();
+        }
+
+        case SDL_MOUSEBUTTONDOWN:
+        /* "Stop" button - go to main menu: */
+        {
+          if (inRect(stopRect, event.button.x, event.button.y ))
+          {
+            finished = 1;
+            playsound(SND_TOCK);
+            break;
+          }
+        }
+        case SDL_KEYDOWN:
+        {
+          finished = 1;
+          playsound(SND_TOCK);
+        }
+      }
+    }
+
+    /* --- make tux blink --- */
+    switch (frame % TUX6)
+    {
+      case 0:    tux_frame = 1; break;
+      case TUX1: tux_frame = 2; break;
+      case TUX2: tux_frame = 3; break;
+      case TUX3: tux_frame = 4; break;
+      case TUX4: tux_frame = 3; break;
+      case TUX5: tux_frame = 2; break;
+      default: tux_frame = 0;
+    }
+
+    if (Tux && tux_frame)
+    {
+      SDL_BlitSurface(Tux->frame[tux_frame - 1], NULL, screen, &Tuxdest);
+//      SDL_UpdateRect(screen, Tuxdest.x+37, Tuxdest.y+40, 70, 45);
+      SDL_UpdateRect(screen, Tuxdest.x, Tuxdest.y, Tuxdest.w, Tuxdest.h);
+
+    }
+    /* Wait so we keep frame rate constant: */
+    while ((SDL_GetTicks() - start) < 33)
+    {
+      SDL_Delay(20);
+    }
+    frame++;
+  }  // End of while (!finished) loop
+
+  SDL_FreeSurface(s1);
+  SDL_FreeSurface(s2);
+  SDL_FreeSurface(s3);
+  SDL_FreeSurface(s4);
+}
+
+
+void main_scmo(menu_options* mo) //set custom menu opts for main
+{
+  mo->ygap = 15;
+}
+
+int run_main_menu(void)
+{
+  const char* menu_text[6] =
+    {N_("Play Alone"),
+     N_("Play With Friends"),
+     N_("Factoroids!"),
+     N_("Help"),
+     N_("More Options"),
+     N_("Quit")};
+  sprite* sprites[6] =
+    {NULL, NULL, NULL, NULL, NULL, NULL};
+  menu_options menu_opts;
+  int choice,ret;
+
+  // Set up the sprites
+  sprites[0] = sprite_list[SPRITE_ALONE];
+  sprites[1] = sprite_list[SPRITE_FRIENDS];
+  sprites[2] = sprite_list[SPRITE_FACTOROIDS];
+  sprites[3] = sprite_list[SPRITE_HELP];
+  sprites[4] = sprite_list[SPRITE_OPTIONS];
+  sprites[5] = sprite_list[SPRITE_QUIT];
+
+
+  //set_default_menu_options(&menu_opts);
+  //menu_opts.ytop = 100;
+  //menu_opts.ygap = 15;
+
+  //This function takes care of all the drawing and receives
+  //user input:
+  choice = choose_menu_item(menu_text,sprites,6,NULL,main_scmo);
+
+  while (choice >= 0) {
+    switch (choice) {
+      case 0: {
+        // All single player modes
+        ret = run_game_menu();
+        break;
+      }
+      case 1: {
+        // Multiplayer games
+        ret = run_multiplay_menu();
+        break;
+      }
+      case 2: {
+        // Factroids et. al.
+        ret = run_activities_menu();
+        break;
+      }
+      case 3: {
+        // Help
+        Opts_SetHelpMode(1);
+        Opts_SetDemoMode(0);
+        if (Opts_GetGlobalOpt(MENU_MUSIC))  //Turn menu music off for game
+          {audioMusicUnload();}
+        game();
+        RecalcTitlePositions();
+        if (Opts_GetGlobalOpt(MENU_MUSIC)) //Turn menu music back on
+          {audioMusicLoad( "tuxi.ogg", -1 );}
+        Opts_SetHelpMode(0);
+        break;
+      }
+      case 4: {
+        // More options
+        ret = run_options_menu();
+        break;
+      }
+      case 5: {
+        // Quit
+        tmdprintf("Exiting main menu\n");
+        return 0;
+      }
+    }
+    menu_opts.starting_entry = choice;
+    choice = choose_menu_item(menu_text,sprites,6,NULL,main_scmo);
+  }
+  return 0;
+}
+                                                     
+#define NUM_GAME_MENU_ITEMS 5
+int run_game_menu(void)
+{
+  const char* menu_text[NUM_GAME_MENU_ITEMS] =
+    {N_("Math Command Training Academy"),
+     N_("Math Command Fleet Missions"),          
+     N_("Play Arcade Game"),
+     N_("Play Custom Game"),
+     N_("Main menu")};
+
+  sprite* sprites[NUM_GAME_MENU_ITEMS] = {NULL, NULL, NULL, NULL, NULL};
+
+  int ret, choice = 0;
+
+  sprites[0] = sprite_list[SPRITE_TRAINING];
+  sprites[1] = sprite_list[SPRITE_CAMPAIGN];
+  sprites[2] = sprite_list[SPRITE_ARCADE];
+  sprites[3] = sprite_list[SPRITE_CUSTOM];
+  sprites[4] = sprite_list[SPRITE_MAIN];
+
+  while (choice >= 0) {
+    choice = choose_menu_item(menu_text,sprites,NUM_GAME_MENU_ITEMS,NULL,NULL);
+    switch (choice) {
+      case 0:
+        ret = run_lessons_menu();
+        break;
+      case 1:
+        ret = start_campaign();
+        break;
+      case 2:
+        ret = run_arcade_menu();
+        break;
+      case 3:
+        ret = run_custom_menu();
+        break;
+      case 4:
+        return 0;
+      default:
+        tmdprintf("choose_menu_item() returned %d--returning\n", choice);
+        return 0;
+    }
+  }
+  return 0;
+}
+
+/*
+Set up and start a turn-based multiplayer game. Some funky heap issues so
+quarantine it behind the return for the time being.
+*/
+int run_multiplay_menu(void)
+{
+  int nplayers = 0;
+  int mode = -1;
+  int difficulty = -1;
+  char npstr[HIGH_SCORE_NAME_LENGTH * 3];
+
+  const char* menu_text[3] =
+    {N_("Score Sweep"),
+     N_("Elimination"),
+     N_("Main menu")};
+
+  //just leech settings from arcade modes
+  const char* diff_menu_text[NUM_MATH_COMMAND_LEVELS + 1] =
+    {N_("Space Cadet"),
+     N_("Scout"),
+     N_("Ranger"),
+     N_("Ace"),
+     N_("Commando"),
+     N_("Main menu")};
+
+
+  sprite* modesprites[3] = {NULL, NULL, NULL};
+  sprite* diffsprites[6] = {NULL, NULL, NULL, NULL, NULL, NULL};
+  // Set up the sprites
+  modesprites[0] = sprite_list[SPRITE_SSWEEP];
+  modesprites[1] = sprite_list[SPRITE_ELIMINATION];
+  modesprites[2] = sprite_list[SPRITE_MAIN];
+  
+  diffsprites[0] = sprite_list[SPRITE_CADET];
+  diffsprites[1] = sprite_list[SPRITE_SCOUT];
+  diffsprites[2] = sprite_list[SPRITE_RANGER];
+  diffsprites[3] = sprite_list[SPRITE_ACE];
+  diffsprites[4] = sprite_list[SPRITE_COMMANDO];
+  diffsprites[5] = sprite_list[SPRITE_MAIN];
+
+  while (1)
+  {
+    //choose difficulty
+    difficulty = choose_menu_item(diff_menu_text, diffsprites, 
+                 NUM_MATH_COMMAND_LEVELS + 1, NULL, NULL);
+
+    if (difficulty == -1 || difficulty >= NUM_MATH_COMMAND_LEVELS)
+      break; //user chose main menu or hit escape
+
+    //choose mode
+    mode = choose_menu_item(menu_text,modesprites,3,NULL,NULL);
+    if (mode == 2 || mode == -1)
+      break;
+
+    //ask how many players
+    while (nplayers <= 0 || nplayers > MAX_PLAYERS)
+    {
+      NameEntry(npstr, _("How many kids are playing?"),
+                       _("(Between 2 and 4 players)"));
+      nplayers = atoi(npstr);
+    }
+
+
+    mp_set_parameter(PLAYERS, nplayers);
+    mp_set_parameter(MODE, mode);
+    mp_set_parameter(DIFFICULTY, difficulty);
+
+    //RUN!
+    mp_run_multiplayer();
+  }
+
+  return 0;
+}
+
+int run_arcade_menu(void)
+{
+  const char* menu_text[7] =
+    {N_("Space Cadet"),
+     N_("Scout"),
+     N_("Ranger"),
+     N_("Ace"),
+     N_("Commando"),
+     N_("Hall Of Fame"),
+     N_("Main menu")};               
+  const char* arcade_config_files[5] =
+    {"arcade/space_cadet",
+     "arcade/scout",
+     "arcade/ranger",
+     "arcade/ace",
+     "arcade/commando"
+    };
+
+  const int arcade_high_score_tables[5] =
+    {CADET_HIGH_SCORE,
+     SCOUT_HIGH_SCORE,
+     RANGER_HIGH_SCORE,
+     ACE_HIGH_SCORE,
+     COMMANDO_HIGH_SCORE
+    };
+  sprite* sprites[7] =
+    {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
+  menu_options menu_opts;
+  int choice,hs_table;
+
+  // Set up the sprites
+  sprites[0] = sprite_list[SPRITE_CADET];
+  sprites[1] = sprite_list[SPRITE_SCOUT];
+  sprites[2] = sprite_list[SPRITE_RANGER];
+  sprites[3] = sprite_list[SPRITE_ACE];
+  sprites[4] = sprite_list[SPRITE_COMMANDO];
+  sprites[5] = sprite_list[SPRITE_TROPHY];
+  sprites[6] = sprite_list[SPRITE_MAIN];
+
+//  set_default_menu_options(&menu_opts);
+//  menu_opts.ytop = 100;
+
+  //This function takes care of all the drawing and receives
+  //user input:
+  choice = choose_menu_item(menu_text,sprites,7,NULL,NULL);
+
+  while (choice >= 0) {
+    if (choice < NUM_MATH_COMMAND_LEVELS) {
+      // Play arcade game
+      if (read_named_config_file(arcade_config_files[choice]))
+      {
+        audioMusicUnload();
+        game();
+        RecalcTitlePositions();
+        if (Opts_GetGlobalOpt(MENU_MUSIC)) {
+          audioMusicLoad( "tuxi.ogg", -1 );
+        }
+        /* See if player made high score list!                        */
+        read_high_scores();  /* Update, in case other users have added to it */
+        hs_table = arcade_high_score_tables[choice];
+        if (check_score_place(hs_table, Opts_LastScore()) < HIGH_SCORES_SAVED){
+
+          char player_name[HIGH_SCORE_NAME_LENGTH * 3];
+
+          /* Get name from player: */
+          HighScoreNameEntry(&player_name[0]);
+          insert_score(player_name, hs_table, Opts_LastScore());
+          /* Show the high scores. Note the user will see his/her */
+          /* achievement even if (in the meantime) another player */
+          /* has in fact already bumped this score off the table. */
+          DisplayHighScores(hs_table);
+          /* save to disk: */
+          /* See "On File Locking" in fileops.c */
+          append_high_score(choice,Opts_LastScore(),&player_name[0]);
+
+#ifdef TUXMATH_DEBUG
+          print_high_scores(stderr);
+#endif
+        }
+      } else {
+        fprintf(stderr, "\nCould not find %s config file\n",arcade_config_files[choice]);
+      }
+
+    } else if (choice == NUM_MATH_COMMAND_LEVELS) {
+      // Display the Hall of Fame
+      DisplayHighScores(CADET_HIGH_SCORE);
+    }
+    else {
+      // Return to main menu
+      return 0;
+    }
+    set_default_menu_options(&menu_opts);
+    menu_opts.starting_entry = choice;
+    choice = choose_menu_item(menu_text,sprites,7,NULL, NULL);
+  }
+
+  return 0;
+}
+
+
+int run_custom_menu(void)
+{
+  const char *s1, *s2, *s3, *s4;
+  s1 = _("Edit 'options' file in your home directory");
+  s2 = _("to create customized game!");
+  s3 = _("Press a key or click your mouse to start game.");
+  s4 = _("See README.txt for more information");
+  ShowMessage(s1, s2, s3, s4);
+
+  if (read_user_config_file()) {
+    if (Opts_GetGlobalOpt(MENU_MUSIC))
+      audioMusicUnload();
+
+    game();
+    RecalcTitlePositions();
+    write_user_config_file();
+
+    if (Opts_GetGlobalOpt(MENU_MUSIC))
+      audioMusicLoad( "tuxi.ogg", -1 );
+  }
+
+  return 0;
+}
+
+int run_activities_menu(void)
+{ 
+  const char* menu_text[3] =
+    {N_("Factors"),
+     N_("Fractions"),
+     N_("Main menu")};
+  const int factoroids_high_score_tables[2] =
+    {FACTORS_HIGH_SCORE, FRACTIONS_HIGH_SCORE};
+  sprite* sprites[3] =
+    {NULL, NULL, NULL};
+  menu_options menu_opts;
+  int choice, hs_table;
+
+  // Set up the sprites
+  sprites[0] = sprite_list[SPRITE_FACTORS];
+  sprites[1] = sprite_list[SPRITE_FRACTIONS];
+  sprites[2] = sprite_list[SPRITE_MAIN];
+
+  set_default_menu_options(&menu_opts);
+  menu_opts.ytop = 100;
+
+  //This function takes care of all the drawing and receives
+  //user input:
+  choice = choose_menu_item(menu_text, sprites, 3, NULL, NULL);
+
+  while (choice >= 0) {
+    switch(choice){
+      case 0:
+          audioMusicUnload();
+          factors();
+	  
+	  if (Opts_GetGlobalOpt(MENU_MUSIC)) {
+	      audioMusicLoad( "tuxi.ogg", -1 );
+	  }
+	  break;
+      case 1:
+          audioMusicUnload(); 
+          fractions();
+	  
+	  if (Opts_GetGlobalOpt(MENU_MUSIC)) {
+	     audioMusicLoad( "tuxi.ogg", -1 );
+	  }
+	  break;
+     case 2:
+          // Return to main menu
+          return 0;
+    }
+
+	hs_table = factoroids_high_score_tables[choice];
+	if (check_score_place(hs_table, Opts_LastScore()) < HIGH_SCORES_SAVED){
+
+	  char player_name[HIGH_SCORE_NAME_LENGTH * 3];
+
+	  /* Get name from player: */
+	  HighScoreNameEntry(&player_name[0]);
+	  insert_score(player_name, hs_table, Opts_LastScore());
+	  /* Show the high scores. Note the user will see his/her */
+	  /* achievement even if (in the meantime) another player */
+	  /* has in fact already bumped this score off the table. */
+	  DisplayHighScores(hs_table);
+	  /* save to disk: */
+	  /* See "On File Locking" in fileops.c */
+	  append_high_score(hs_table,Opts_LastScore(),&player_name[0]);
+
+#ifdef TUXMATH_DEBUG
+	  print_high_scores(stderr);
+#endif
+	}
+       else {
+	fprintf(stderr, "\nCould not find config file\n");
+      }  
+
+    menu_opts.starting_entry = choice;
+    choice = choose_menu_item(menu_text,sprites,3,NULL,NULL);
+
+
+  }
+
+
+  return 0; 
+}
+
+
+int run_options_menu(void)
+{
+  /*
+    // Use the following version if we get "Settings" implemented
+  const unsigned char* menu_text[5] =
+    {(const unsigned char*)N_("Settings"),
+     (const unsigned char*)N_("Demo"),
+     (const unsigned char*)N_("Credits"),
+     (const unsigned char*)N_("Project Info"),
+     (const unsigned char*)N_("Main Menu")};
+  sprite* sprites[5] =
+    {NULL, NULL, NULL, NULL, NULL};
+  */
+  const char* menu_text[4] =
+    {N_("Demo"),
+     N_("Project Info"),
+     N_("Credits"),
+     N_("Main Menu")};
+
+  sprite* sprites[4] =
+    {NULL, NULL, NULL, NULL};
+  int n_menu_entries = 4;
+  menu_options menu_opts;
+  int choice;
+
+  // Set up the sprites
+  sprites[0] = sprite_list[SPRITE_ARCADE];
+  sprites[1] = sprite_list[SPRITE_HELP];
+  sprites[2] = sprite_list[SPRITE_CREDITS];
+  sprites[3] = sprite_list[SPRITE_MAIN];
+
+  //set_default_menu_options(&menu_opts);
+  //menu_opts.ytop = 100;
+
+  //This function takes care of all the drawing and receives
+  //user input:
+  choice = choose_menu_item(menu_text, sprites, n_menu_entries, NULL, NULL);
+
+  while (choice >= 0) {
+    switch (choice) {
+      /*
+    case 0: {
+      // Settings
+      NotImplemented();
+      break;
+      }*/
+    case 0: {
+      // Demo
+      if (read_named_config_file("demo"))
+      {
+        audioMusicUnload();
+        game();
+        RecalcTitlePositions();
+        if (Opts_GetGlobalOpt(MENU_MUSIC)) {
+          audioMusicLoad( "tuxi.ogg", -1 );
+        }
+      } else {
+        fprintf(stderr, "\nCould not find demo config file\n");
+      }
+
+      break;
+    }
+    case 1: {
+      // Project Info
+      //NotImplemented();
+      ShowMessage(_("TuxMath is free and open-source!"),
+                  _("You can help make it better by reporting problems,"),
+                  _("suggesting improvements, or adding code."),
+                  _("Discuss the future at tuxmath-devel at lists.sourceforge.net"));
+      break;
+    }
+    case 2: {
+      // Credits
+      //TitleScreen_unload_media();
+      credits();
+      //TitleScreen_load_media();
+      break;
+    }
+    case 3: {
+      // Main menu
+      return 0;
+    }
+    }
+
+    set_default_menu_options(&menu_opts);
+    menu_opts.starting_entry = choice;
+    choice = choose_menu_item(menu_text,sprites,n_menu_entries,NULL,NULL);
+  }
+
+  return 0;
+}
+
+
+void lessons_scmo(menu_options* mo)
+{
+mo->ytop = 30;
+}
+/* Display a list of tuxmath config files in the missions directory   */
+/* and allow the player to pick one (AKA "Lessons").                  */
+
+/* returns 0 if user pressed escape
+ *         1 if config was set correctly
+ */
+int run_lessons_menu(void)
+{
+  int chosen_lesson = -1;
+  menu_options menu_opts;
+  sprite** star_sprites = NULL;
+
+  /* Set up sprites (as long as gold star list is valid) */
+  if (lesson_list_goldstars != NULL)
+  {
+    int i;
+    star_sprites = (sprite**)malloc(num_lessons * sizeof(sprite*));
+    for (i = 0; i < num_lessons; i++)
+    {
+      if (lesson_list_goldstars[i])
+        star_sprites[i] = sprite_list[SPRITE_GOLDSTAR];
+      else
+        star_sprites[i] = sprite_list[SPRITE_NO_GOLDSTAR];
+    }
+  }
+//  set_default_menu_options(&menu_opts);
+//  ytop = 30;
+
+  //This function takes care of all the drawing and receives
+  //user input:
+  chosen_lesson = choose_menu_item((const char**)lesson_list_titles, star_sprites, num_lessons, NULL, &lessons_scmo);
+
+  while (chosen_lesson >= 0)
+  {
+    if (Opts_GetGlobalOpt(MENU_SOUND))
+      playsound(SND_POP);
+
+    /* Re-read global settings first in case any settings were */
+    /* clobbered by other lesson or arcade games this session: */
+    read_global_config_file();
+    /* Now read the selected file and play the "mission": */
+    if (read_named_config_file(lesson_list_filenames[chosen_lesson]))
+    {
+      if (Opts_GetGlobalOpt(MENU_MUSIC))  //Turn menu music off for game
+        {audioMusicUnload();}
+
+
+      game();
+      RecalcTitlePositions();
+
+      /* If successful, display Gold Star for this lesson! */
+      if (MC_MissionAccomplished())
+      {
+        lesson_list_goldstars[chosen_lesson] = 1;
+        star_sprites[chosen_lesson] = sprite_list[SPRITE_GOLDSTAR];
+       /* and save to disk: */
+        write_goldstars();
+      }
+
+      if (Opts_GetGlobalOpt(MENU_MUSIC)) //Turn menu music back on
+        {audioMusicLoad("tuxi.ogg", -1);}
+    }
+    else  // Something went wrong - could not read lesson config file:
+    {
+      fprintf(stderr, "\nCould not find file: %s\n", lesson_list_filenames[chosen_lesson]);
+      chosen_lesson = -1;
+    }
+    // Let the user choose another lesson; start with the screen and
+    // selection that we ended with
+    set_default_menu_options(&menu_opts);
+    menu_opts.starting_entry = chosen_lesson;
+    chosen_lesson = choose_menu_item((const char**)lesson_list_titles, star_sprites, num_lessons, &menu_opts, &lessons_scmo);
+  }
+  if (star_sprites)
+  {
+    free(star_sprites);
+    star_sprites = NULL;
+  }
+
+  if (chosen_lesson < 0)
+    return 0;
+  else
+    return 1;
+}
+
+
+/* Sets the user home directory in a tree of possible users     */
+/* -1 indicates that the user wants to quit without logging in, */
+/* 0 indicates that a choice has been made.                     */
+int run_login_menu(void)
+{
+  int n_login_questions = 0;
+  char **user_login_questions = NULL;
+  int n_users = 0;
+  char **user_names = NULL;
+  menu_options menu_opts;
+  int chosen_login = -1;
+  int level;
+  int i;
+  char *trailer_quit = "Quit";
+  char *trailer_back = "Back";
+  SDLMod mod;
+
+  // Check for & read user_login_questions file
+  n_login_questions = read_user_login_questions(&user_login_questions);
+
+  // Check for & read user_menu_entries file
+  n_users = read_user_menu_entries(&user_names);
+
+  if (n_users == 0)
+    return 0;   // a quick exit, there's only one user
+
+  // Check for a highscores file
+  if (high_scores_found_in_user_dir())
+    set_high_score_path();
+
+  level = 0;
+  set_default_menu_options(&menu_opts);
+  if (n_login_questions > 0)
+    menu_opts.title = user_login_questions[0];
+  menu_opts.trailer = trailer_quit;
+
+  while (n_users) {
+    // Get the user choice
+    chosen_login = choose_menu_item((const char**)user_names, NULL, n_users, &menu_opts, NULL);
+    // Determine whether there were any modifier (CTRL) keys pressed
+    mod = SDL_GetModState();
+    if (chosen_login == -1 || chosen_login == n_users) {
+      // User pressed escape or selected Quit/Back, handle by quitting
+      // or going up a level
+      if (level == 0) {
+        // We are going to quit without logging in.
+        // Clean up memory (prob. not necessary, but prevents Valgrind errors!)
+        for (i = 0; i < n_login_questions; i++)
+          free(user_login_questions[i]);
+        free(user_login_questions);
+        for (i = 0; i < n_users; i++)
+          free(user_names[i]);
+        free(user_names);
+        return -1;
+      }
+      else {
+        // Go back up one level of the directory tree
+        user_data_dirname_up();
+        level--;
+        menu_opts.starting_entry = -1;
+      }
+    }
+    else {
+      // User chose an entry, set it up
+      user_data_dirname_down(user_names[chosen_login]);
+      level++;
+      menu_opts.starting_entry = 0;
+    }
+    // Check for a highscores file
+    if (high_scores_found_in_user_dir())
+      set_high_score_path();
+    // Free the entries from the previous menu
+    for (i = 0; i < n_users; i++)
+      free(user_names[i]);
+    free(user_names);
+    user_names = NULL;
+    // If the CTRL key was pressed, choose this as the identity, even
+    // if there is a lower level to the hierarchy
+    if (mod & KMOD_CTRL)
+      break;
+    // Set the title appropriately for the next menu
+    if (level < n_login_questions)
+      menu_opts.title = user_login_questions[level];
+    else
+      menu_opts.title = NULL;
+    if (level == 0)
+      menu_opts.trailer = trailer_quit;
+    else
+      menu_opts.trailer = trailer_back;
+    // Check to see if there are more choices to be made
+    n_users = read_user_menu_entries(&user_names);
+  }
+
+  // The user home directory is set, clean up remaining memory
+  for (i = 0; i < n_login_questions; i++)
+    free(user_login_questions[i]);
+  free(user_login_questions);
+
+  // Signal success
+  return 0;
+}
+
+
+/****************************************************************/
+/* choose_menu_item: menu navigation utility function           */
+/* (the function returns the index for the selected menu item)  */
+/* -1 indicates that the user pressed escape                    */
+/****************************************************************/
+int choose_menu_item(const char **menu_text, sprite **menu_sprites, int n_menu_entries, menu_options* custom_mo, void (*set_custom_menu_opts)(menu_options*) )
+{
+  // Pixel renderings of menu text choices
+  SDL_Surface **menu_item_unselected = NULL;
+  SDL_Surface **menu_item_selected = NULL;
+  // Display region for menu choices
+  SDL_Rect *menu_text_rect = NULL;
+  // Translucent mouse "buttons" around menu text
+  SDL_Rect *menu_button_rect = NULL;
+  // Menu sprite locations
+  SDL_Rect *menu_sprite_rect = NULL;
+
+  // The section of the background that the menu rects actually cover
+  SDL_Rect *back_text_rect = NULL,
+           *back_button_rect = NULL,
+           *back_sprite_rect = NULL;
+  SDL_Rect left_arrow_rect, right_arrow_rect;
+  SDL_Rect temp_rect; //temporary copy of a dest rect that may be written to by SDL_BlitSurface
+
+  menu_options menu_opts;
+
+  Uint32 frame_counter = 0;
+  Uint32 frame_start = 0;       //For keeping frame rate constant
+  Uint32 frame_now = 0;
+  int stop = 0;
+  int loc = 0;                  //The currently selected menu item
+  int old_loc = 1;
+  int loc_screen_start = 0;     //The number of the top entry on current screen
+  int old_loc_screen_start = 0;
+  int redraw = 0;
+  int n_entries_per_screen = 0;
+  int buttonheight = 0;
+  int i = 0;
+  int imod = 0;                 // i % n_entries_per_screen
+  int tux_frame = 0;
+  int click_flag = 1;
+  int use_sprite = 0;
+  int warp_mouse = 0;
+  int title_offset = 0;
+  int have_trailer = 0;
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "Entering choose_menu_item():\n");
+#endif
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr,"%d menu entries:\n",n_menu_entries);
+  for (i = 0; i < n_menu_entries; i++)
+    fprintf(stderr,"%s\n",menu_text[i]);
+#endif
+
+  if (custom_mo == NULL)
+    set_default_menu_options(&menu_opts);
+  else
+    menu_opts = *custom_mo;
+  if (set_custom_menu_opts != NULL)
+    set_custom_menu_opts(&menu_opts);
+
+  tmdprintf("Allocating memory\n");
+  /**** Memory allocation for menu text  ****/
+  title_offset = 0;
+  if (menu_opts.title != NULL)
+    title_offset = 1;
+  if (menu_opts.trailer != NULL)
+    have_trailer = 1;
+  menu_item_unselected = (SDL_Surface**)malloc((n_menu_entries+title_offset+have_trailer) * sizeof(SDL_Surface*));
+  menu_item_selected = (SDL_Surface**)malloc((n_menu_entries+title_offset+have_trailer) * sizeof(SDL_Surface*));
+  if (menu_item_unselected == NULL || menu_item_selected == NULL) {
+    free(menu_item_unselected);
+    free(menu_item_selected);
+    return -2;  // error
+  }
+
+  /**** Render the menu choices                               ****/
+  if (title_offset)
+  {
+    menu_item_unselected[0] = BlackOutline( _(menu_opts.title),default_font,&red);
+    // It will never be selected, so we don't have to do anything for selected.
+    menu_item_selected[0] = NULL;
+  }
+  for (i = 0; i < n_menu_entries; i++)
+  {
+    menu_item_unselected[i+title_offset] = BlackOutline( _(menu_text[i]), default_font, &white );
+    menu_item_selected[i+title_offset] = BlackOutline( _(menu_text[i]), default_font, &yellow);
+  }
+  if (have_trailer) {
+    menu_item_unselected[n_menu_entries+title_offset] = BlackOutline( _(menu_opts.trailer), default_font, &white );
+    menu_item_selected[n_menu_entries+title_offset] = BlackOutline( _(menu_opts.trailer), default_font, &yellow);
+  }
+  // We won't need the menu_text again, so now we can keep track of
+  // the total entries including the title & trailer
+  n_menu_entries += title_offset+have_trailer;
+
+//  recalcMenuPositions();
+
+  /**** Calculate the menu item heights and the number of     ****/
+  /**** entries per screen                                    ****/
+  if (menu_opts.buttonheight <= 0) {
+    buttonheight = 0;
+    for (i = 0; i < n_menu_entries; i++)
+      if (buttonheight < menu_item_unselected[i]->h)
+        buttonheight = menu_item_unselected[i]->h;
+    buttonheight += 10;
+  } else
+    buttonheight = menu_opts.buttonheight;
+
+  // First try using the whole screen; if we need more than one
+  // screen, then we have to save space for the arrows by respecting
+  // ybottom
+  n_entries_per_screen = (int) (screen->h - menu_opts.ytop+menu_opts.ygap)/(buttonheight + menu_opts.ygap);
+  if (n_entries_per_screen < n_menu_entries)
+    n_entries_per_screen = (int) (menu_opts.ybottom - menu_opts.ytop+menu_opts.ygap)/(buttonheight + menu_opts.ygap);
+
+  if (n_entries_per_screen > n_menu_entries)
+    n_entries_per_screen = n_menu_entries;
+
+  /**** Memory allocation for current screen rects  ****/
+  menu_text_rect = (SDL_Rect*) malloc(n_entries_per_screen * sizeof(SDL_Rect));
+  menu_button_rect = (SDL_Rect*) malloc(n_entries_per_screen * sizeof(SDL_Rect));
+  back_text_rect = (SDL_Rect*) malloc(n_entries_per_screen * sizeof(SDL_Rect));
+  back_button_rect = (SDL_Rect*) malloc(n_entries_per_screen * sizeof(SDL_Rect));
+  if (menu_text_rect == NULL || menu_button_rect == NULL ||
+      back_text_rect == NULL || back_button_rect == NULL) {
+    free(menu_text_rect);
+    free(menu_button_rect);
+    free(back_text_rect);
+    free(back_button_rect);
+    return -2;
+  }
+  if (menu_sprites != NULL) {
+    menu_sprite_rect = (SDL_Rect*) malloc(n_entries_per_screen * sizeof(SDL_Rect));
+    back_sprite_rect = (SDL_Rect*) malloc(n_entries_per_screen * sizeof(SDL_Rect));
+    if (menu_sprite_rect == NULL || back_sprite_rect == NULL) {
+      free(menu_sprite_rect);
+      free(back_sprite_rect);
+      return -2;
+    }
+  }
+
+  /**** Define the locations of graphical elements on the screen ****/
+  /* Arrow buttons in right lower corner, inset by 20 pixels     */
+  /* with a 10 pixel space between:                              */
+  if (images[IMG_RIGHT])
+  {
+    right_arrow_rect.w = images[IMG_RIGHT]->w;
+    right_arrow_rect.h = images[IMG_RIGHT]->h;
+    right_arrow_rect.x = screen->w - images[IMG_RIGHT]->w - 20;
+    right_arrow_rect.y = screen->h - images[IMG_RIGHT]->h - 20;
+  }
+
+  if (images[IMG_LEFT])
+  {
+    left_arrow_rect.w = images[IMG_LEFT]->w;
+    left_arrow_rect.h = images[IMG_LEFT]->h;
+    left_arrow_rect.x = right_arrow_rect.x - 10 - images[IMG_LEFT]->w;
+    left_arrow_rect.y = screen->h - images[IMG_LEFT]->h - 20;
+  }
+  /* Red "Stop" circle in upper right corner to go back to main menu: */
+  if (images[IMG_STOP])
+  {
+    stopRect.w = images[IMG_STOP]->w;
+    stopRect.h = images[IMG_STOP]->h;
+    stopRect.x = screen->w - images[IMG_STOP]->w;
+    stopRect.y = 0;
+  }
+
+  /* Set initial menu rect sizes. The widths will change depending      */
+  /* on the size of the text displayed in each rect.  Set the widths    */
+  /* for the current screen of menu items.                              */
+  loc = menu_opts.starting_entry + title_offset;  // Initially selected item
+  loc_screen_start = loc - (loc % n_entries_per_screen);
+  if (loc_screen_start < 0 || loc_screen_start*n_entries_per_screen > n_menu_entries)
+    loc_screen_start = 0;  // in case starting_entry was -1 (or wasn't set)
+  imod = loc-loc_screen_start;
+  for (i = 0; i < n_entries_per_screen; i++)
+  {
+    menu_button_rect[i].x = menu_opts.xleft;
+    menu_text_rect[i].x = menu_opts.xleft + 15;  // 15 is left gap
+    if (menu_sprites != NULL)
+      menu_text_rect[i].x += 60;  // 40 is sprite width, 20 is gap
+    if (i > 0)
+      menu_text_rect[i].y = menu_text_rect[i - 1].y + buttonheight + menu_opts.ygap;
+    else
+      menu_text_rect[i].y = menu_opts.ytop;
+    menu_button_rect[i].y = menu_text_rect[i].y-5;
+    menu_text_rect[i].h = buttonheight-10;
+    menu_button_rect[i].h = buttonheight;
+    menu_button_rect[i].w = menu_text_rect[i].w = 0;
+    if (i + loc_screen_start < n_menu_entries) {
+      menu_text_rect[i].w = menu_item_unselected[i+loc_screen_start]->w;
+      menu_button_rect[i].w = menu_text_rect[i].w + 30;
+    }
+    if (menu_sprite_rect != NULL) {
+      menu_sprite_rect[i].x = menu_button_rect[i].x+3;
+      menu_sprite_rect[i].y = menu_button_rect[i].y+3;
+      menu_sprite_rect[i].w = 40;
+      menu_sprite_rect[i].h = 50;
+    }
+  }
+
+  if (menu_opts.button_same_width)
+    set_buttons_max_width(menu_button_rect,back_button_rect,n_entries_per_screen);
+
+  for (i = 0; i < n_entries_per_screen; ++i)
+  {
+    if (menu_button_rect)
+    {
+      back_button_rect[i] = menu_button_rect[i];
+      back_button_rect[i].x -= Backrect.x;
+      back_button_rect[i].y -= Backrect.y;
+    }
+    if (menu_text_rect)
+    {
+      back_text_rect[i] = menu_text_rect[i];
+      back_text_rect[i].x -= Backrect.x;
+      back_text_rect[i].y -= Backrect.y;
+    }
+    if (menu_sprite_rect)
+    {
+      back_sprite_rect[i] = menu_sprite_rect[i];
+      back_sprite_rect[i].x -= Backrect.x;
+      back_sprite_rect[i].y -= Backrect.y;
+    }
+  }
+
+  /**** Draw background, title, and Tux:                            ****/
+  if (current_bkg() )
+    SDL_BlitSurface(current_bkg(), NULL, screen, &Backrect);
+  if (images[IMG_MENU_TITLE])
+    SDL_BlitSurface(images[IMG_MENU_TITLE], NULL, screen, &Titledest);
+  if (Tux && Tux->frame[0])
+    SDL_BlitSurface(Tux->frame[0], NULL, screen, &Tuxdest);
+  SDL_UpdateRect(screen, 0, 0, 0 ,0);
+
+  /* Move mouse to current button: */
+  cursor.x = menu_button_rect[imod].x + menu_button_rect[imod].w/2;
+  cursor.y = menu_button_rect[imod].y + menu_button_rect[imod].h/2;
+//  SDL_WarpMouse(cursor.x, cursor.y);
+  SDL_WM_GrabInput(SDL_GRAB_OFF);
+
+
+  /******** Main loop:                                *********/
+  redraw = 1;  // force a full redraw on first pass
+  old_loc_screen_start = loc_screen_start;
+  while (SDL_PollEvent(&event));  // clear pending events
+  while (!stop)
+  {
+    frame_start = SDL_GetTicks();         /* For keeping frame rate constant.*/
+
+    while (SDL_PollEvent(&event))
+    {
+      switch (event.type)
+      {
+        case SDL_QUIT:
+        {
+          cleanup();
+          //exit(0);
+          break;
+        }
+
+        case SDL_MOUSEMOTION:
+        {
+          loc = -1;  // By default, don't be in any entry
+          for (i = 0; (i < n_entries_per_screen) && (loc_screen_start + i < n_menu_entries); i++)
+          {
+            if (inRect(menu_button_rect[i], event.motion.x, event.motion.y))
+            {
+              // Play sound if loc is being changed:
+              if (Opts_GetGlobalOpt(MENU_SOUND) && (old_loc != loc_screen_start + i))
+              {
+                playsound(SND_TOCK);
+              }
+              loc = loc_screen_start + i;
+              break;   /* from for loop */
+            }
+          }
+
+          /* "Left" button - make click if button active: */
+          if (inRect(left_arrow_rect, event.motion.x, event.motion.y))
+          {
+            if (loc_screen_start - n_entries_per_screen >= 0)
+            {
+              if (Opts_GetGlobalOpt(MENU_SOUND) && click_flag)
+              {
+                playsound(SND_TOCK);
+                click_flag = 0;
+              }
+            }
+            break;  /* from case switch */
+          }
+
+          /* "Right" button - go to next page: */
+          else if (inRect(right_arrow_rect, event.motion.x, event.motion.y ))
+          {
+            if (loc_screen_start + n_entries_per_screen < n_menu_entries)
+            {
+              if (Opts_GetGlobalOpt(MENU_SOUND) && click_flag)
+              {
+                playsound(SND_TOCK);
+                click_flag = 0;
+              }
+            }
+            break;  /* from case switch */
+          }
+
+          else  // Mouse outside of arrow rects - re-enable click sound:
+          {
+            click_flag = 1;
+            break;  /* from case switch */
+          }
+        }
+
+        case SDL_MOUSEBUTTONDOWN:
+        {
+          /* Choose a menu entry by mouse click */
+          for (i = 0; (i < n_entries_per_screen) && (loc_screen_start + i < n_menu_entries); i++)
+          {
+            if (inRect(menu_button_rect[i], event.button.x, event.button.y))
+            {
+              if (Opts_GetGlobalOpt(MENU_SOUND))
+              {
+                playsound(SND_POP);
+              }
+
+              loc = loc_screen_start + i;
+              stop = 1;
+              break;
+            }
+          }
+
+          /* "Left" button - go to previous page: */
+          if (inRect(left_arrow_rect, event.button.x, event.button.y))
+          {
+            if (loc_screen_start - n_entries_per_screen >= 0)
+            {
+              //loc = loc_screen_start - n_entries_per_screen;
+              loc_screen_start -= n_entries_per_screen;
+              loc = -1;  // nothing selected
+              if (Opts_GetGlobalOpt(MENU_SOUND))
+              {
+                playsound(SND_TOCK);
+              }
+              break;
+            }
+          }
+
+          /* "Right" button - go to next page: */
+          if (inRect( right_arrow_rect, event.button.x, event.button.y ))
+          {
+            if (loc_screen_start + n_entries_per_screen < n_menu_entries)
+            {
+              //loc = loc_screen_start + n_entries_per_screen;
+              loc_screen_start += n_entries_per_screen;
+              loc = -1;  // nothing selected
+              if (Opts_GetGlobalOpt(MENU_SOUND))
+              {
+                playsound(SND_TOCK);
+              }
+              break;
+            }
+          }
+
+          /* "Stop" button - go to main menu: */
+          if (inRect(stopRect, event.button.x, event.button.y ))
+          {
+            stop = 2;
+            playsound(SND_TOCK);
+            break;
+          }
+        } /* End of case SDL_MOUSEDOWN */
+
+
+        case SDL_KEYDOWN:
+        {
+          /* Proceed according to particular key pressed: */
+          switch (event.key.keysym.sym)
+          {
+            case SDLK_ESCAPE:
+            {
+              stop = 2;
+              break;
+            }
+
+            case SDLK_RETURN:
+            case SDLK_SPACE:
+            case SDLK_KP_ENTER:
+            {
+              if (Opts_GetGlobalOpt(MENU_SOUND))
+                playsound(SND_POP);
+              stop = 1;
+              break;
+            }
+
+
+            /* Go to previous page, if present: */
+            case SDLK_LEFT:
+            case SDLK_PAGEUP:
+            {
+              if (Opts_GetGlobalOpt(MENU_SOUND))
+                playsound(SND_TOCK);
+              if (loc_screen_start - n_entries_per_screen >= 0) {
+                loc_screen_start -= n_entries_per_screen;
+                loc = -1;
+              }
+              //  {loc = loc_screen_start - n_entries_per_screen;}
+              break;
+            }
+
+
+            /* Go to next page, if present: */
+            case SDLK_RIGHT:
+            case SDLK_PAGEDOWN:
+            {
+              if (Opts_GetGlobalOpt(MENU_SOUND))
+                playsound(SND_TOCK);
+              if (loc_screen_start + n_entries_per_screen < n_menu_entries) {
+                loc_screen_start += n_entries_per_screen;
+                loc = -1;
+              }
+              //  {loc = (loc_screen_start + n_entries_per_screen);}
+              break;
+            }
+
+            /* Go up one entry, if present: */
+            case SDLK_UP:
+            {
+              if (Opts_GetGlobalOpt(MENU_SOUND))
+                playsound(SND_TOCK);
+              if (loc > title_offset)
+                {loc--;}
+              else if (n_menu_entries <= n_entries_per_screen) {
+                loc = n_menu_entries-1;  // wrap around if only 1 screen
+              }
+              else if (loc == -1 && loc_screen_start > 0) {
+                loc = loc_screen_start-1;
+                loc_screen_start -= n_entries_per_screen;
+              }
+              if (loc != old_loc)
+                warp_mouse = 1;
+              break;
+            }
+
+
+            /* Go down one entry, if present: */
+            case SDLK_DOWN:
+            {
+              if (Opts_GetGlobalOpt(MENU_SOUND))
+                playsound(SND_TOCK);
+              if (loc >= 0 && loc + 1 < n_menu_entries)
+                {loc++;}
+              else if (n_menu_entries <= n_entries_per_screen)
+                loc = title_offset;       // wrap around if only 1 screen
+              else if (loc == -1)
+                loc = loc_screen_start;
+              if (loc != old_loc)
+                warp_mouse = 1;
+              break;
+           }
+
+
+            /* Toggle screen mode: */
+            case SDLK_F10:
+            {
+              SwitchScreenMode();
+              RecalcTitlePositions();
+              RecalcMenuPositions(&n_entries_per_screen,
+                                  n_menu_entries,
+                                  &menu_opts,
+                                  set_custom_menu_opts,
+                                  &menu_button_rect,
+                                  &menu_sprite_rect,
+                                  &menu_text_rect,
+                                  &back_button_rect,
+                                  &back_sprite_rect,
+                                  &back_text_rect,
+                                  &left_arrow_rect,
+                                  &right_arrow_rect);
+              //we're unsure how the entries might shuffle, so return to start
+              loc_screen_start = 0;
+              redraw = 1;
+              break;
+            }
+
+            /* Toggle menu music: */
+            case SDLK_F11:
+            {
+              if (Opts_GetGlobalOpt(MENU_MUSIC))
+              {
+                audioMusicUnload( );
+                Opts_SetGlobalOpt(MENU_MUSIC, 0);
+              }
+              else
+              {
+                Opts_SetGlobalOpt(MENU_MUSIC, 1);
+                audioMusicLoad("tuxi.ogg", -1);
+              }
+              break;
+            }
+#ifdef TESTING_CAMPAIGN
+            case SDLK_c:
+            {
+              start_campaign();
+              RecalcTitlePositions();
+              RecalcMenuPositions(&n_entries_per_screen,
+                                  n_menu_entries,
+                                  &menu_opts,
+                                  set_custom_menu_opts,
+                                  &menu_button_rect,
+                                  &menu_sprite_rect,
+                                  &menu_text_rect,
+                                  &back_button_rect,
+                                  &back_sprite_rect,
+                                  &back_text_rect,
+                                  &left_arrow_rect,
+                                  &right_arrow_rect);
+              redraw = 1;
+            }
+
+#endif
+            default:
+            {
+              /* Some other key - do nothing. */
+            }
+
+            break;  /* To get out of _outer_ switch/case statement */
+          }  /* End of key switch statement */
+        }  // End of case SDL_KEYDOWN in outer switch statement
+      }  // End event switch statement
+      if (handle_easter_egg(&event) )
+        redraw = 1;
+      else
+        ; //egg_active = 0;
+    }  // End SDL_PollEvent while loop
+
+
+
+    // Make sure the menu title is not selected
+    if (loc == 0 && title_offset)
+      loc = title_offset;
+
+    /* Redraw screen: */
+    if (loc >= 0)
+      loc_screen_start = loc - (loc % n_entries_per_screen);
+    if (old_loc_screen_start != loc_screen_start)
+      redraw = 1;
+    if (redraw)
+    {
+      tmdprintf("Updating entire screen\n");
+      /* This is a full-screen redraw */
+      /* Redraw background, title, stop button, and Tux: */
+      if (!current_bkg() || screen->flags & SDL_FULLSCREEN )
+        SDL_FillRect(screen, &screen->clip_rect, 0); //clear to black
+      if (current_bkg())
+        SDL_BlitSurface(current_bkg(), NULL, screen, &Backrect);
+      if (images[IMG_MENU_TITLE])
+        SDL_BlitSurface(images[IMG_MENU_TITLE], NULL, screen, &Titledest);
+      if (images[IMG_STOP])
+        SDL_BlitSurface(images[IMG_STOP], NULL, screen, &stopRect);
+      if (Tux->frame[0])
+        SDL_BlitSurface(Tux->frame[0], NULL, screen, &Tuxdest);
+      /* Redraw the menu entries */
+      for (imod = 0; imod < n_entries_per_screen; imod++)
+        menu_button_rect[imod].w = 0;  // so undrawn buttons don't affect width
+      for (i = loc_screen_start, imod = 0; i < loc_screen_start+n_entries_per_screen && i < n_menu_entries; i++, imod++) {
+        menu_text_rect[imod].w = menu_item_unselected[i]->w;
+        if (i >= title_offset) {
+          menu_button_rect[imod].w = menu_text_rect[imod].w + 30;
+          if (menu_sprites != NULL)
+            menu_button_rect[imod].w += 60;
+        }
+      }
+
+      if (menu_opts.button_same_width)
+        set_buttons_max_width(menu_button_rect,back_button_rect,n_entries_per_screen);
+      // Make sure the menu title mouse button didn't get turned on
+      if (loc_screen_start == 0 && title_offset)
+        menu_button_rect[0].w = 0;
+      for (i = loc_screen_start, imod = 0; i < loc_screen_start+n_entries_per_screen && i < n_menu_entries; i++, imod++) {
+        if (i == loc) {  //Draw text in yellow
+          DrawButton(&menu_button_rect[imod], 10, SEL_RGBA);
+          SDL_BlitSurface(menu_item_selected[loc], NULL, screen, &menu_text_rect[imod]);
+        }
+        else {          //Draw text in white
+          if (menu_button_rect[imod].w > 0)
+            DrawButton(&menu_button_rect[imod], 10, REG_RGBA);
+          SDL_BlitSurface(menu_item_unselected[i], NULL, screen, &menu_text_rect[imod]);
+        }
+        if (menu_sprites != NULL && (i >= title_offset) && menu_sprites[i-title_offset] != NULL)
+          SDL_BlitSurface(menu_sprites[i-title_offset]->default_img, NULL, screen, &menu_sprite_rect[imod]);
+      }
+
+      /* --- draw 'left' and 'right' buttons --- */
+      if (n_menu_entries > n_entries_per_screen) {
+        if (loc_screen_start > 0)        // i.e. not on first page
+        {
+            SDL_BlitSurface(images[IMG_LEFT], NULL, screen, &left_arrow_rect);
+        }
+        else  /* Draw grayed-out left button: */
+        {
+          SDL_BlitSurface(images[IMG_LEFT_GRAY], NULL, screen, &left_arrow_rect);
+        }
+
+        if (loc_screen_start + n_entries_per_screen < n_menu_entries)  // not on last page
+        {
+          SDL_BlitSurface(images[IMG_RIGHT], NULL, screen, &right_arrow_rect);
+        }
+        else  /* Draw grayed-out right button: */
+        {
+          SDL_BlitSurface(images[IMG_RIGHT_GRAY], NULL, screen, &right_arrow_rect);
+        }
+      }
+
+      SDL_Flip(screen);//SDL_UpdateRect(screen, 0, 0, 0 ,0);
+    } else if (old_loc != loc) {
+      // This is not a full redraw, but the selected entry did change.
+      // By just redrawing the old and new selections, we avoid flickering.
+      if (old_loc >= 0) {
+        imod = old_loc-loc_screen_start;
+        use_sprite = (menu_sprites != NULL && old_loc >= title_offset && menu_sprites[old_loc-title_offset] != NULL);
+        temp_rect = menu_button_rect[imod];
+        SDL_FillRect(screen, &temp_rect, 0);
+        SDL_BlitSurface(current_bkg(), &back_button_rect[imod], screen, &temp_rect);   // redraw background
+        if (use_sprite) {
+          // Some of the sprites extend beyond the menu button, so we
+          // have to make sure we redraw in the sprite rects, too
+          SDL_BlitSurface(current_bkg(), &back_sprite_rect[imod], screen, &temp_rect);
+        }
+        DrawButton(&menu_button_rect[imod], 10, REG_RGBA);  // draw button
+        //temp_rect = menu_text_rect[imod];
+        SDL_BlitSurface(menu_item_unselected[old_loc], NULL, screen, &menu_text_rect[imod]);  // draw text
+        if (use_sprite) {
+          temp_rect = menu_sprite_rect[imod];
+          tmdprintf("Sprite %d at (%d %d)\n",  imod, temp_rect.x, temp_rect.y);
+          SDL_BlitSurface(menu_sprites[old_loc-title_offset]->default_img, NULL, screen, &temp_rect);
+          // Also update the sprite rect (in some cases the sprite
+          // extends beyond the menu button)
+          SDL_UpdateRect(screen, menu_sprite_rect[imod].x, menu_sprite_rect[imod].y, menu_sprite_rect[imod].w, menu_sprite_rect[imod].h);
+        }
+        SDL_UpdateRect(screen, menu_button_rect[imod].x, menu_button_rect[imod].y, menu_button_rect[imod].w, menu_button_rect[imod].h);
+      }
+      if (loc >= 0) {
+        imod = loc-loc_screen_start;
+        use_sprite = (menu_sprites != NULL && loc >= title_offset && menu_sprites[loc] != NULL);
+        temp_rect = menu_button_rect[imod];
+        SDL_BlitSurface(current_bkg(), &(back_button_rect[imod]), screen, &temp_rect);
+        if (use_sprite)
+        {
+          temp_rect = menu_sprite_rect[imod];
+          SDL_BlitSurface(current_bkg(), &(back_sprite_rect[imod]), screen, &temp_rect);
+        }
+        DrawButton(&menu_button_rect[imod], 10, SEL_RGBA);
+        SDL_BlitSurface(menu_item_selected[loc], NULL, screen, &menu_text_rect[imod]);
+        if (use_sprite) {
+          menu_sprites[loc-title_offset]->cur = 0;  // start at beginning of animation sequence
+          SDL_BlitSurface(menu_sprites[loc-title_offset]->frame[menu_sprites[loc-title_offset]->cur], NULL, screen, &menu_sprite_rect[imod]);
+          SDL_UpdateRect(screen, menu_sprite_rect[imod].x, menu_sprite_rect[imod].y, menu_sprite_rect[imod].w, menu_sprite_rect[imod].h);
+          next_frame(menu_sprites[loc-title_offset]);
+        }
+        SDL_UpdateRect(screen, menu_button_rect[imod].x, menu_button_rect[imod].y, menu_button_rect[imod].w, menu_button_rect[imod].h);
+        tmdprintf("Updating rect: %d %d %d %d\n", menu_button_rect[imod].x, menu_button_rect[imod].y, menu_button_rect[imod].w, menu_button_rect[imod].h);
+      }
+    } else if (frame_counter % 5 == 0 && loc >= 0) {
+      // No user input changed anything, but check to see if we need to
+      // animate the sprite
+      if (menu_sprites != NULL && loc >= title_offset && menu_sprites[loc-title_offset] != NULL) {
+        imod = loc-loc_screen_start;
+        //SDL_BlitSurface(current_bkg, &menu_button_rect[imod], screen, &menu_button_rect[imod]);
+        temp_rect = menu_sprite_rect[imod];
+        SDL_BlitSurface(current_bkg(), &back_sprite_rect[imod], screen, &temp_rect);
+        DrawButton(&menu_button_rect[imod], 10, SEL_RGBA);
+        //SDL_BlitSurface(menu_item_selected[loc], NULL, screen, &menu_text_rect[imod]);
+        // Note: even though the whole button was redrawn, we don't
+        // have to redraw the text & background as long as we don't
+        // update that rect. If something else changes and we go to
+        // full-screen updates, then remove the "commenting-out" on
+        // the two lines above
+        SDL_BlitSurface(menu_sprites[loc-title_offset]->frame[menu_sprites[loc-title_offset]->cur], NULL, screen, &menu_sprite_rect[imod]);
+        SDL_UpdateRect(screen, menu_sprite_rect[imod].x, menu_sprite_rect[imod].y, menu_sprite_rect[imod].w, menu_sprite_rect[imod].h);
+        next_frame(menu_sprites[loc-title_offset]);
+      }
+    }
+
+    redraw = 0;
+
+    /* Move the mouse pointer if there is only a single screen */
+    if (warp_mouse && n_menu_entries <= n_entries_per_screen) {
+      imod = loc - loc_screen_start;
+      cursor.x = menu_button_rect[imod].x + (menu_button_rect[imod].w / 2);
+      cursor.y = menu_button_rect[imod].y + (3 * menu_button_rect[imod].h / 4);
+//      SDL_WarpMouse(cursor.x, cursor.y);
+      warp_mouse = 0;
+    }
+
+    old_loc = loc;
+    old_loc_screen_start = loc_screen_start;
+
+
+
+    /* --- make Tux blink --- */
+    switch (frame_counter % TUX6)
+    {
+      case 0:    tux_frame = 1; break;
+      case TUX1: tux_frame = 2; break;
+      case TUX2: tux_frame = 3; break;
+      case TUX3: tux_frame = 4; break;
+      case TUX4: tux_frame = 3; break;
+      case TUX5: tux_frame = 2; break;
+      default: tux_frame = 0;
+    }
+
+    if (Tux && tux_frame)
+    {
+      /* Redraw background to keep edges anti-aliased properly: */
+      SDL_BlitSurface(current_bkg(),&Tuxdest, screen, &Tuxdest);
+      SDL_BlitSurface(Tux->frame[tux_frame - 1], NULL, screen, &Tuxdest);
+      SDL_UpdateRect(screen, Tuxdest.x, Tuxdest.y, Tuxdest.w, Tuxdest.h);
+      //SDL_UpdateRect(screen, 0, 0, 0, 0);
+    }
+
+    if (egg_active) { //if we need to, draw the egg cursor
+      //who knows why GetMouseState() doesn't take Sint16's...
+      SDL_GetMouseState((int*)(&cursor.x), (int*)(&cursor.y));
+      cursor.x -= egg->w / 2; //center vertically
+      SDL_BlitSurface(egg, NULL, screen, &cursor);
+      SDL_UpdateRect(screen, cursor.x, cursor.y, cursor.w, cursor.h);
+    }
+
+    /* Wait so we keep frame rate constant: */
+    frame_now = SDL_GetTicks();
+    if (frame_now < frame_start)
+      frame_start = frame_now;  // in case the timer wraps around
+    if (frame_now - frame_start < 33)
+      SDL_Delay(33-(frame_now-frame_start));
+
+    frame_counter++;
+  }  // End !stop while loop
+
+
+  /***** User made a choice, clean up and return the choice.   ******/
+
+  /* --- clear graphics before leaving function --- */
+  for (i = 0; i < n_menu_entries; i++)
+  {
+    SDL_FreeSurface(menu_item_unselected[i]);
+    SDL_FreeSurface(menu_item_selected[i]);
+  }
+  free(menu_item_unselected);
+  free(menu_item_selected);
+  free(menu_text_rect);
+  free(menu_button_rect);
+  free(menu_sprite_rect);
+
+  /* Return the value of the chosen item (-1 indicates escape) */
+  if (stop == 2)
+    return -1;
+  else
+    return loc - title_offset;
+}
+
+
+
+void set_buttons_max_width(SDL_Rect *menu_button_rect,
+                           SDL_Rect *back_button_rect, int n)
+{
+  int i,max;
+
+  max = 0;
+  for (i = 0; i < n; i++)
+    if (max < menu_button_rect[i].w)
+      max = menu_button_rect[i].w;
+
+  for (i = 0; i < n; i++)
+    menu_button_rect[i].w = back_button_rect[i].w = max;
+
+  tmdprintf("All buttons at width %d\n", max);
+}
+
+// Was in playgame.c in tuxtype:
+
+/*************************************************/
+/* TransWipe: Performs various wipes to new bkgs */
+/*************************************************/
+/*
+ * Given a wipe request type, and any variables
+ * that wipe requires, will perform a wipe from
+ * the current screen image to a new one.
+ */
+void TransWipe(SDL_Surface* newbkg, int type, int var1, int var2)
+{
+    int i, j, x1, x2, y1, y2;
+    int step1, step2, step3, step4;
+    int frame;
+    SDL_Rect src;
+    SDL_Rect dst;
+
+    if (!screen)
+    {
+#ifdef TUXMATH_DEBUG
+      fprintf(stderr, "TransWipe(): screen not valid!\n");
+#endif
+      return;
+    }
+
+    if (!newbkg)
+    {
+#ifdef TUXMATH_DEBUG
+      fprintf(stderr, "TransWipe(): newbkg not valid!\n");
+#endif
+      return;
+    }
+
+    numupdates = 0;
+    frame = 0;
+
+    if(newbkg->w == screen->w && newbkg->h == screen->h) {
+        if( type == RANDOM_WIPE )
+            type = (RANDOM_WIPE * ((float) rand()) / (RAND_MAX+1.0));
+
+        switch( type ) {
+            case WIPE_BLINDS_VERT: {
+ #ifdef TUXMATH_DEBUG
+                fprintf(stderr, "--+ Doing 'WIPE_BLINDS_VERT'\n");
+#endif
+                /* var1 is num of divisions
+                   var2 is how many frames animation should take */
+                if( var1 < 1 ) var1 = 1;
+                if( var2 < 1 ) var2 = 1;
+                step1 = screen->w / var1;
+                step2 = step1 / var2;
+
+                for(i = 0; i <= var2; i++)
+                {
+                    for(j = 0; j <= var1; j++)
+                    {
+                        x1 = step1 * (j - 0.5) - i * step2 + 1;
+                        x2 = step1 * (j - 0.5) + i * step2 + 1;
+                        src.x = x1;
+                        src.y = 0;
+                        src.w = step2;
+                        src.h = screen->h;
+                        dst.x = x2;
+                        dst.y = 0;
+                        dst.w = step2;
+                        dst.h = screen->h;
+
+                        SDL_BlitSurface(newbkg, &src, screen, &src);
+                        SDL_BlitSurface(newbkg, &dst, screen, &dst);
+
+                        AddRect(&src, &src);
+                        AddRect(&dst, &dst);
+                    }
+                    UpdateScreen(&frame);
+                }
+
+                src.x = 0;
+                src.y = 0;
+                src.w = screen->w;
+                src.h = screen->h;
+                SDL_BlitSurface(newbkg, NULL, screen, &src);
+                SDL_Flip(screen);
+
+                break;
+            } case WIPE_BLINDS_HORIZ: {
+#ifdef TUXMATH_DEBUG
+                fprintf(stderr, "--+ Doing 'WIPE_BLINDS_HORIZ'\n");
+#endif
+                /* var1 is num of divisions
+                   var2 is how many frames animation should take */
+                if( var1 < 1 ) var1 = 1;
+                if( var2 < 1 ) var2 = 1;
+                step1 = screen->h / var1;
+                step2 = step1 / var2;
+
+                for(i = 0; i <= var2; i++) {
+                    for(j = 0; j <= var1; j++) {
+                        y1 = step1 * (j - 0.5) - i * step2 + 1;
+                        y2 = step1 * (j - 0.5) + i * step2 + 1;
+                        src.x = 0;
+                        src.y = y1;
+                        src.w = screen->w;
+                        src.h = step2;
+                        dst.x = 0;
+                        dst.y = y2;
+                        dst.w = screen->w;
+                        dst.h = step2;
+
+                        SDL_BlitSurface(newbkg, &src, screen, &src);
+                        SDL_BlitSurface(newbkg, &dst, screen, &dst);
+
+                        AddRect(&src, &src);
+                        AddRect(&dst, &dst);
+                    }
+                    UpdateScreen(&frame);
+                }
+
+                src.x = 0;
+                src.y = 0;
+                src.w = screen->w;
+                src.h = screen->h;
+                SDL_BlitSurface(newbkg, NULL, screen, &src);
+                SDL_Flip(screen);
+
+                break;
+            } case WIPE_BLINDS_BOX: {
+#ifdef TUXMATH_DEBUG
+                fprintf(stderr, "--+ Doing 'WIPE_BLINDS_BOX'\n");
+#endif
+                /* var1 is num of divisions
+                   var2 is how many frames animation should take */
+                if( var1 < 1 ) var1 = 1;
+                if( var2 < 1 ) var2 = 1;
+                step1 = screen->w / var1;
+                step2 = step1 / var2;
+                step3 = screen->h / var1;
+                step4 = step1 / var2;
+
+                for(i = 0; i <= var2; i++) {
+                    for(j = 0; j <= var1; j++) {
+                        x1 = step1 * (j - 0.5) - i * step2 + 1;
+                        x2 = step1 * (j - 0.5) + i * step2 + 1;
+                        src.x = x1;
+                        src.y = 0;
+                        src.w = step2;
+                        src.h = screen->h;
+                        dst.x = x2;
+                        dst.y = 0;
+                        dst.w = step2;
+                        dst.h = screen->h;
+
+                        SDL_BlitSurface(newbkg, &src, screen, &src);
+                        SDL_BlitSurface(newbkg, &dst, screen, &dst);
+
+                        AddRect(&src, &src);
+                        AddRect(&dst, &dst);
+                        y1 = step3 * (j - 0.5) - i * step4 + 1;
+                        y2 = step3 * (j - 0.5) + i * step4 + 1;
+                        src.x = 0;
+                        src.y = y1;
+                        src.w = screen->w;
+                        src.h = step4;
+                        dst.x = 0;
+                        dst.y = y2;
+                        dst.w = screen->w;
+                        dst.h = step4;
+                        SDL_BlitSurface(newbkg, &src, screen, &src);
+                        SDL_BlitSurface(newbkg, &dst, screen, &dst);
+                        AddRect(&src, &src);
+                        AddRect(&dst, &dst);
+                    }
+                    UpdateScreen(&frame);
+                }
+
+                src.x = 0;
+                src.y = 0;
+                src.w = screen->w;
+                src.h = screen->h;
+                SDL_BlitSurface(newbkg, NULL, screen, &src);
+                SDL_Flip(screen);
+
+                break;
+            } default:
+                break;
+        }
+    }
+#ifdef TUXMATH_DEBUG
+      fprintf(stderr, "->TransWipe(): FINISH\n");
+#endif
+}
+
+
+
+/************************
+UpdateScreen : Update the screen and increment the frame num
+***************************/
+void UpdateScreen(int *frame) {
+  int i;
+
+  /* -- First erase everything we need to -- */
+  for (i = 0; i < numupdates; i++)
+    if (blits[i].type == 'E')
+      SDL_LowerBlit(blits[i].src, blits[i].srcrect, screen, blits[i].dstrect);
+//        SNOW_erase();
+
+  /* -- then draw -- */
+  for (i = 0; i < numupdates; i++)
+    if (blits[i].type == 'D')
+      SDL_BlitSurface(blits[i].src, blits[i].srcrect, screen, blits[i].dstrect);
+//        SNOW_draw();
+
+/* -- update the screen only where we need to! -- */
+//        if (SNOW_on)
+//                SDL_UpdateRects(screen, SNOW_add( (SDL_Rect*)&dstupdate, numupdates ), SNOW_rects);
+//        else
+    SDL_UpdateRects(screen, numupdates, dstupdate);
+
+  numupdates = 0;
+  *frame = *frame + 1;
+}
+
+
+/******************************
+AddRect: Don't actually blit a surface,
+    but add a rect to be updated next
+    update
+*******************************/
+void AddRect(SDL_Rect* src, SDL_Rect* dst) {
+    /*borrowed from SL's alien (and modified)*/
+
+    struct blit    *update;
+
+    if (!src || !dst)
+    {
+#ifdef TUXMATH_DEBUG
+     fprintf(stderr, "AddRect(): src or dst invalid!\n");
+#endif
+      return;
+    }
+
+    update = &blits[numupdates++];
+
+    update->srcrect->x = src->x;
+    update->srcrect->y = src->y;
+    update->srcrect->w = src->w;
+    update->srcrect->h = src->h;
+    update->dstrect->x = dst->x;
+    update->dstrect->y = dst->y;
+    update->dstrect->w = dst->w;
+    update->dstrect->h = dst->h;
+    update->type = 'I';
+}
+
+/***********************
+ InitEngine
+ ***********************/
+void InitEngine(void) {
+    int i;
+
+    /* --- Set up the update rectangle pointers --- */
+
+    for (i = 0; i < MAX_UPDATES; ++i) {
+        blits[i].srcrect = &srcupdate[i];
+        blits[i].dstrect = &dstupdate[i];
+    }
+}
+
+
+void set_default_menu_options(menu_options *menu_opts)
+{
+  menu_opts->starting_entry = 0;
+  menu_opts->xleft = screen->w / 2 - screen->w * 3 / 32;
+  menu_opts->ytop = screen->h / 2 - 140;
+  // Leave room for arrows at the bottom:
+  menu_opts->ybottom = screen->h - images[IMG_LEFT]->h - 20;
+  menu_opts->buttonheight = -1;
+  menu_opts->ygap = 10;
+  menu_opts->button_same_width = 1;
+  menu_opts->title = NULL;
+  menu_opts->trailer = NULL;
+}
+
+/* Recalculate on-screen locations for title screen elements */
+void RecalcTitlePositions()
+{
+  Backrect = current_bkg()->clip_rect;
+  Backrect.x = (screen->w - Backrect.w) / 2;
+  Backrect.y = (screen->h - Backrect.h) / 2;
+
+  Titledest.x = 0;
+  Titledest.y = 0;
+
+  Tuxdest.x = 0;
+  Tuxdest.y = screen->h - Tuxdest.h;
+
+  beak.x = Tuxdest.x + 70;
+  beak.y = Tuxdest.y + 60;
+  beak.w = beak.h = 50;
+
+  stopRect.x = screen->w - stopRect.w;
+  stopRect.y = 0;
+}
+
+/* Recalculate on-screen locations for menus when screen dimensions change */
+/* Perhaps consider generalizing this for use in initial menu calculations? */
+void RecalcMenuPositions(int* numentries,
+                         int totalentries,
+                         menu_options* mo,
+                         void (*set_custom_menu_opts)(menu_options*),
+                         SDL_Rect** menu_button_rect,
+                         SDL_Rect** menu_sprite_rect,
+                         SDL_Rect** menu_text_rect,
+                         SDL_Rect** back_button_rect,
+                         SDL_Rect** back_sprite_rect,
+                         SDL_Rect** back_text_rect,
+                         SDL_Rect* left_arrow_rect,
+                         SDL_Rect* right_arrow_rect)
+{
+  int i;
+  SDL_Rect* old_mbr = *menu_button_rect;
+  SDL_Rect* old_msr = *menu_sprite_rect;
+  SDL_Rect* old_mtr = *menu_text_rect;
+  SDL_Rect* old_bbr = *back_button_rect;
+  SDL_Rect* old_bsr = *back_sprite_rect;
+  SDL_Rect* old_btr = *back_text_rect;
+
+  int old_ne = *numentries;
+  int buttonheight = old_mbr->h; //height shouldn't change
+  int textwidth = old_mtr->w; //neither should width =P
+
+  right_arrow_rect->x = screen->w - images[IMG_RIGHT]->w - 20;
+  right_arrow_rect->y = screen->h - images[IMG_RIGHT]->h - 20;
+  left_arrow_rect->x = right_arrow_rect->x - 10 - images[IMG_LEFT]->w;
+  left_arrow_rect->y = screen->h - images[IMG_LEFT]->h - 20;
+
+  set_default_menu_options(mo);
+  if (set_custom_menu_opts != NULL)
+    set_custom_menu_opts(mo);
+
+  *numentries     = (int)(screen->h - mo->ytop+mo->ygap)/(buttonheight + mo->ygap);
+  if (*numentries < totalentries)
+    *numentries = (int)(mo->ybottom - mo->ytop+mo->ygap)/(buttonheight + mo->ygap);
+  if (*numentries > totalentries)
+    *numentries = totalentries;
+
+
+
+
+  /**** Memory allocation for new screen rects  ****/
+  *menu_text_rect = (SDL_Rect*) malloc(*numentries * sizeof(SDL_Rect));
+  *menu_button_rect = (SDL_Rect*) malloc(*numentries * sizeof(SDL_Rect));
+  *menu_sprite_rect = (SDL_Rect*) malloc(*numentries * sizeof(SDL_Rect));
+  *back_text_rect = (SDL_Rect*) malloc(*numentries * sizeof(SDL_Rect));
+  *back_button_rect = (SDL_Rect*) malloc(*numentries * sizeof(SDL_Rect));
+  *back_sprite_rect = (SDL_Rect*) malloc(*numentries * sizeof(SDL_Rect));
+  if (*menu_text_rect == NULL ||
+      *back_text_rect == NULL ||
+      *menu_button_rect == NULL ||
+      *back_button_rect == NULL ||
+      *menu_sprite_rect == NULL ||
+      *back_sprite_rect == NULL) {
+    free(*menu_text_rect);
+    free(*menu_button_rect);
+    free(*menu_sprite_rect);
+    free(*back_text_rect);
+    free(*back_button_rect);
+    free(*back_sprite_rect);
+    *menu_text_rect = old_mtr;
+    *menu_button_rect = old_mbr;
+    *menu_sprite_rect = old_msr;
+    *numentries = old_ne;
+    return;
+  }
+  else {
+    free(old_mtr);
+    free(old_mbr);
+    free(old_msr);
+    free(old_btr);
+    free(old_bbr);
+    free(old_bsr);
+  }
+
+
+  //note: the [0] notation is merely to avoid typing out (*menu_xxx_rect)[i]
+  for (i = 0; i < *numentries; i++)
+  {
+    menu_button_rect[0][i].x = mo->xleft;
+    menu_text_rect[0][i].x = mo->xleft + 15;  // 15 is left gap
+    menu_text_rect[0][i].x += 60;  // for now, assume we have a sprite
+    if (i > 0)
+      menu_text_rect[0][i].y = menu_text_rect[0][i - 1].y + buttonheight + mo->ygap;
+    else
+      menu_text_rect[0][i].y = mo->ytop;
+
+    menu_button_rect[0][i].y = menu_text_rect[0][i].y - 5;
+    menu_text_rect[0][i].h = buttonheight - 10;
+    menu_button_rect[0][i].h = buttonheight;
+
+    menu_text_rect[0][i].w = textwidth;
+    menu_button_rect[0][i].w = menu_text_rect[0][i].w + 30;
+
+    if (menu_sprite_rect != NULL) {
+      menu_sprite_rect[0][i].x = menu_button_rect[0][i].x + 3;
+      menu_sprite_rect[0][i].y = menu_button_rect[0][i].y + 3;
+      menu_sprite_rect[0][i].w = 40;
+      menu_sprite_rect[0][i].h = 50;
+    }
+    tmdprintf("***Rects[%d]****\n", i);
+    tmdprintf("%3d %3d %3d %3d\n", menu_button_rect[0][i].x, menu_button_rect[0][i].y, menu_button_rect[0][i].w, menu_button_rect[0][i].h);
+    tmdprintf("%3d %3d %3d %3d\n", menu_text_rect[0][i].x, menu_text_rect[0][i].y, menu_text_rect[0][i].w, menu_text_rect[0][i].h);
+    tmdprintf("%3d %3d %3d %3d\n", menu_sprite_rect[0][i].x, menu_sprite_rect[0][i].y, menu_sprite_rect[0][i].w, menu_sprite_rect[0][i].h);
+    tmdprintf("***************\n");
+  }
+  for (i = 0; i < *numentries; ++i)
+  {
+    back_button_rect[0][i] = menu_button_rect[0][i];
+    back_button_rect[0][i].x -= Backrect.x;
+    back_button_rect[0][i].y -= Backrect.y;
+
+    back_text_rect[0][i] = menu_text_rect[0][i];
+    back_text_rect[0][i].x -= Backrect.x;
+    back_text_rect[0][i].y -= Backrect.y;
+
+    back_sprite_rect[0][i] = menu_sprite_rect[0][i];
+    back_sprite_rect[0][i].x -= Backrect.x;
+    back_sprite_rect[0][i].y -= Backrect.y;
+  }
+
+}
+
+int handle_easter_egg(const SDL_Event* evt)
+  {
+  static int eggtimer = 0;
+  int tuxframe;
+
+  // Avoid segfaults if needed images not available:
+  if (!Tux || !egg)
+  {
+    fprintf(stderr,
+      "handle_easter_egg() - needed images not avail, bailing out\n");
+    egg_active = 0;
+    return 1;
+  }
+
+  tuxframe = Tux->num_frames;
+
+
+  if (egg_active) //are we using the egg cursor?
+    {
+
+    if (eggtimer < SDL_GetTicks() ) //time's up
+      {
+      SDL_ShowCursor(SDL_ENABLE);
+      //SDL_FillRect(screen, &cursor, 0);
+      SDL_BlitSurface(current_bkg(), NULL, screen, &Backrect); //cover egg up once more
+      SDL_WarpMouse(cursor.x, cursor.y);
+      SDL_UpdateRect(screen, cursor.x, cursor.y, cursor.w, cursor.h); //egg->x, egg->y, egg->w, egg->h);
+      egg_active = 0;
+      }
+    return 1;
+    }
+  else //if not, see if the user clicked Tux's beak
+    {
+    eggtimer = 0;
+    if (evt->type == SDL_MOUSEBUTTONDOWN &&
+          inRect(beak, evt->button.x, evt->button.y) )
+      {
+      SDL_ShowCursor(SDL_DISABLE);
+
+      //animate
+      while (tuxframe != 0)
+        {
+        SDL_BlitSurface(Tux->frame[--tuxframe], NULL, screen, &Tuxdest);
+        SDL_UpdateRect(screen, Tuxdest.x, Tuxdest.y, Tuxdest.w, Tuxdest.h);
+        SDL_Delay(GOBBLE_ANIM_MS / Tux->num_frames);
+        }
+
+      eggtimer = SDL_GetTicks() + EASTER_EGG_MS;
+      egg_active = 1;
+      SDL_WarpMouse(Tuxdest.x + Tuxdest.w / 2, Tuxdest.y + Tuxdest.h - egg->h);
+
+      }
+
+    return 0;
+    }
+  }

Added: tuxmath/trunk/src/titlescreen.h
===================================================================
--- tuxmath/trunk/src/titlescreen.h	                        (rev 0)
+++ tuxmath/trunk/src/titlescreen.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,160 @@
+/***************************************************************************
+ -  file: titlescreen.h
+ -  description: header for the tuxtype-derived files in tuxmath
+                            ------------------
+
+    David Bruce - 2006.
+    email                : <dbruce at tampabay.rr.com>
+                           <tuxmath-devel at lists.sourceforge.net>
+***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+
+
+
+#ifndef TITLESCREEN_H
+#define TITLESCREEN_H
+
+#define to_upper(c) (((c) >= 'a' && (c) <= 'z') ? (c) -32 : (c))
+#define COL2RGB( col ) SDL_MapRGB( screen->format, col->r, col->g, col->b )
+
+//#define FNLEN        200
+
+
+#include <string.h>
+#include <math.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#include "SDL.h"
+#include "SDL_image.h"
+#include "SDL_mixer.h"
+#include "SDL_ttf.h"
+
+#ifndef MACOSX
+//#include "config.h"
+#endif
+
+#include "tuxmath.h"
+#include "loaders.h"
+
+
+// Options that affect how menus are presented
+typedef struct {
+  int starting_entry;
+  int xleft, ytop, ybottom;   // left, top, and bottom borders
+  int buttonheight; // size of menu item button  (-1 if calculated)
+  int ygap;  // vertical gap between entries
+  int button_same_width; // should all buttons have the same width?
+  char *title;
+  char *trailer;
+} menu_options;
+
+
+
+
+#define menu_font  "AndikaDesRevA.ttf"  /*  "GenAI102.ttf" */
+#define menu_font_size       18
+
+#define ttf_font  "AndikaDesRevA.ttf"  /*   "GenAI102.ttf" */
+#define ttf_font_size        18
+
+#define MAX_LESSONS 100
+#define MAX_NUM_WORDS   500
+#define MAX_WORD_SIZE   8
+
+//MAX_UPDATES needed for TransWipe() and friends:
+#define MAX_UPDATES 180
+
+
+#define WAIT_MS                               2500
+#define FRAMES_PER_SEC                        50
+#define FULL_CIRCLE                           140
+
+
+/* Title sequence constants */
+#define PRE_ANIM_FRAMES                       10
+#define PRE_FRAME_MULT                        3
+#define MENU_SEP                              20
+
+/* paths */
+
+
+
+
+
+//extern SDL_Surface *screen;
+//extern TTF_Font  *font;
+extern SDL_Event  event;
+
+SDL_Surface* current_bkg(); //appropriate background for current video mode
+
+#define MUSIC_FADE_OUT_MS        80
+
+enum {
+    WIPE_BLINDS_VERT,
+    WIPE_BLINDS_HORIZ,
+    WIPE_BLINDS_BOX,
+    RANDOM_WIPE,
+
+    NUM_WIPES
+};
+// End of code from tuxtype's globals.h
+
+
+/* --- SETUP MENU OPTIONS --- */
+
+#define TITLE_MENU_ITEMS                5
+#define TITLE_MENU_DEPTH                4
+
+#define OPTIONS_SUBMENU                 4
+#define GAME_OPTIONS_SUBMENU                       3
+#define ARCADE_SUBMENU                        2
+#define ROOTMENU                        1
+
+
+/* --- timings for tux blinking --- */
+#define TUX1                            115
+#define TUX2                            118
+#define TUX3                            121
+#define TUX4                            124
+#define TUX5                            127
+#define TUX6                            130
+
+#define EASTER_EGG_MS  5000 //length of time to replace cursor
+#define GOBBLE_ANIM_MS 1000 //duration of the gobbling animation
+
+/********************************/
+/* "Global" Function Prototypes */
+/********************************/
+
+/*In titlescreen.c */
+void TitleScreen(void);
+int ChooseMission(void);  //FIXME really should be in fileops.c
+int choose_menu_item(const char **menu_text, 
+                     sprite **menu_sprites, 
+                     int n_menu_entries, 
+                     menu_options* custom_mo, 
+                     void (*set_custom_menu_opts)(menu_options*) );
+void set_default_menu_options(menu_options *);
+
+
+
+/* in audio.c  (from tuxtype): */
+void playsound(int snd);
+void audioMusicLoad(char* musicFilename, int repeatQty);
+void audioMusicUnload(void);
+void audioMusicPlay(Mix_Music* musicData, int repeatQty);
+
+#endif //TITLESCREEN_H

Added: tuxmath/trunk/src/tuxmath.c
===================================================================
--- tuxmath/trunk/src/tuxmath.c	                        (rev 0)
+++ tuxmath/trunk/src/tuxmath.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,84 @@
+/*
+  tuxmath.c
+
+  Main function for TuxMath
+  Calls functions in other modules (eg, "setup", "title", "game", etc.)
+  as needed.
+
+  Original source code by Bill Kendrick, New Breed Software
+  bill at newbreedsoftware.com
+  http://www.newbreedsoftware.com/
+
+  Part of "Tux4Kids" Project
+  http://www.tux4kids.com/
+  
+  August 26, 2001 - August 28, 2001
+
+  Largely rewritten by David Bruce, Karl Ove Hufthammer,
+  and Tim Holy.
+  2006-2007
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+/* (tuxmath.h brings in "gettext.h" and <locale.h> */
+#include "tuxmath.h"
+#include "setup.h"
+#include "titlescreen.h"
+
+#ifdef WIN32
+#define TUXLOCALE "./locale"
+#else
+#define TUXLOCALE LOCALEDIR
+#endif
+
+#ifdef LINEBREAK
+//#include "../linebreak/linebreak.h"
+#include "linewrap.h"
+#endif
+
+int main(int argc, char * argv[])
+{
+  const char *s1, *s2, *s3, *s4;
+
+  s1 = setlocale(LC_ALL, "");
+  s2 = bindtextdomain(PACKAGE, TUXLOCALE);
+  s3 = bind_textdomain_codeset(PACKAGE, "UTF-8");
+  s4 = textdomain(PACKAGE);
+
+#ifdef TUXMATH_DEBUG
+  fprintf(stderr, "PACKAGE = %s\n", PACKAGE);
+  fprintf(stderr, "TUXLOCALE = %s\n", TUXLOCALE);
+  fprintf(stderr, "setlocale(LC_ALL, \"\") returned: %s\n", s1);
+  fprintf(stderr, "bindtextdomain(PACKAGE, TUXLOCALE) returned: %s\n", s2);
+  fprintf(stderr, "bind_textdomain_codeset(PACKAGE, \"UTF-8\") returned: %s\n", s3);
+  fprintf(stderr, "textdomain(PACKAGE) returned: %s\n", s4);
+  fprintf(stderr, "gettext(\"Help\"): %s\n\n", gettext("Help"));
+  fprintf(stderr, "After gettext() call\n");
+#endif
+
+  /*
+  const char* brief[5] = {
+    N_("I'm so glad you've come!"),
+    " ",
+    N_("The penguins need your help! Comets are falling from the sky, and are melting the penguins' igloos. To save their homes, we need you to find the secret code that will zap each comet."),
+    ""
+  };
+  linewrap_initialize();
+  linewrap_list(brief, wrapped_lines, 40, MAX_LINES, MAX_LINEWIDTH);
+
+  int i;
+  i = 0;
+  while (strlen(wrapped_lines[i]) > 0)
+    printf("%s\n",wrapped_lines[i++]);
+  linewrap_cleanup();
+  return 0;
+  */
+
+  setup(argc, argv);
+  TitleScreen();  /* Run the game! */
+  cleanup();
+  return 0;
+}
+

Added: tuxmath/trunk/src/tuxmath.h
===================================================================
--- tuxmath/trunk/src/tuxmath.h	                        (rev 0)
+++ tuxmath/trunk/src/tuxmath.h	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,82 @@
+/*
+  tuxmath.h
+
+  For TuxMath
+  Contains global data for configuration of math questions and
+  for general game options, as well as constants and defaults.
+
+  Author: David Bruce <dbruce at tampabay.rr.com>, (C) 2006
+
+
+  Part of "Tux4Kids" Project
+  http://www.tux4kids.org/
+      
+  Added March 2, 2006
+
+  Copyright: See COPYING file that comes with this distribution
+  (briefly - GNU GPL v2 or later)
+*/
+
+
+
+#ifndef TUXMATH_H
+#define TUXMATH_H
+
+#include "config.h"
+
+// Translation stuff (now works for Mac and Win too!): 
+#include "gettext.h"
+#include <locale.h>
+#define _(String) gettext (String)
+#define gettext_noop(String) String
+#define N_(String) gettext_noop (String)
+
+#include <wchar.h>
+
+#include "SDL.h"
+#include "SDL_ttf.h"
+#include "SDL_image.h"
+
+#ifndef NOSOUND
+#include "SDL_mixer.h"
+#endif
+
+//#define NOSOUND
+
+#ifndef GLOBALS_H
+#include "globals.h"
+#endif
+
+
+/* Global data gets 'externed' here: */
+extern int fs_res_x;
+extern int fs_res_y;
+
+extern SDL_Color black;
+extern SDL_Color gray;
+extern SDL_Color dark_blue;
+extern SDL_Color red;
+extern SDL_Color white;
+extern SDL_Color yellow;
+
+extern SDL_Surface* screen; /* declared in setup.c; also used in game.c, options.c, fileops.c, credits.c, titlescreen.c */
+extern SDL_Surface* images[];    /* declared in setup.c, used in same files as screen */
+extern SDL_Surface* flipped_images[];
+#define NUM_BLENDED_IGLOOS 15
+extern SDL_Surface* blended_igloos[];
+extern int flipped_img_lookup[];
+
+extern TTF_Font  *default_font;
+extern TTF_Font  *help_font;
+extern int glyph_offset;
+
+
+#ifndef NOSOUND
+extern Mix_Chunk* sounds[];    /* declared in setup.c; also used in fileops.c, playsound.c */
+extern Mix_Music* musics[];    /* declared in setup.c; also used in fileops.c, game.c  */
+#endif
+
+
+/* NOTE: default values for math options are now in mathcards.h */
+
+#endif

Added: tuxmath/trunk/src/tuxmathadmin.c
===================================================================
--- tuxmath/trunk/src/tuxmathadmin.c	                        (rev 0)
+++ tuxmath/trunk/src/tuxmathadmin.c	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,908 @@
+/*
+  tuxmathadmin.c
+
+  Administer user tuxmath accounts: create accounts, clear gold stars, etc.
+
+  by Tim Holy
+  holy at wustl.edu
+
+  Part of "Tux4Kids" Project
+  http://www.tux4kids.com/
+  Subversion repository:
+  https://svn.debian.alioth.org/tux4kids/tuxmath/
+
+ 
+  December 3, 2007
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+// error.h not always available - e.g. MacOSX, BeOS:
+#ifdef HAVE_ERROR_H
+#include <error.h>
+#else
+#define error(status, errnum, rest...) \
+  fsync(stdout); fprintf(stderr, ## rest); exit(status)
+#endif
+
+// The next two are for mkdir and umask
+#include <sys/types.h>
+#include <sys/stat.h>
+// The next is needed for opendir
+#include <dirent.h>
+// The next is for isspace
+#include <ctype.h>
+// The next is for fsync
+#include <unistd.h>
+
+
+#ifdef BUILD_MINGW32
+#define USER_MENU_ENTRIES_FILENAME "user_menu_entries.txt"
+#define HIGHSCORE_FILENAME "highscores.txt"
+#define GOLDSTAR_FILENAME "goldstars.txt"
+#else
+#define USER_MENU_ENTRIES_FILENAME "user_menu_entries"
+#define HIGHSCORE_FILENAME "highscores"
+#define GOLDSTAR_FILENAME "goldstars"
+#endif
+
+#define PATH_MAX 4096
+#define MAX_USERS 100000
+#define ADMINVERSION "0.1.1"
+
+void display_help(void);
+void usage(int err, char * cmd);
+int extract_variable(FILE *fp, const char *varname, char** value);
+int directory_crawl(const char *path);
+void free_directories(int n);
+void create_homedirs(const char *path,const char *file);
+void config_highscores(const char *path,int level);
+void unconfig_highscores(const char *path);
+void clear_highscores(const char *path);
+void clear_goldstars(const char *path);
+void consolidate_logs(const char *path);
+void clear_logs(const char *path);
+void clear_file(const char *path,const char *filename,const char *invoke_name);
+char* eatwhite(char *buf);
+
+char *directory[MAX_USERS];
+int directory_level[MAX_USERS];
+
+int main(int argc, char *argv[])
+{
+  int i;
+  FILE *fp;
+  DIR *dir;
+
+  int is_creatinghomedirs = 0;
+  int is_confighighscores = 0;
+  int is_unconfighighscores = 0;
+  int is_clearinggoldstars = 0;
+  int is_clearinghighscores = 0;
+  int is_consolidatinglogs = 0;
+  int is_clearinglogs = 0;
+  char *path = NULL;
+  char *file = NULL;
+  int level = 0;
+  int success;
+
+  // Null-out global directory pointers
+  for (i = 0; i < MAX_USERS; i++)
+    directory[i] = NULL;
+
+  if (argc < 2) {
+    display_help();
+    exit(EXIT_FAILURE);
+  }
+
+  // Check global config file for a homedir path (must be uncommented)
+  fp = fopen(DATA_PREFIX "/missions/options", "r");
+  if (fp) {
+    extract_variable(fp,"homedir",&path);
+    fclose(fp);
+  }
+
+  // Parse the command line options
+  for (i = 1; i < argc; i++) {
+    if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
+      display_help();
+      exit(EXIT_SUCCESS);
+    }
+    else if (strcmp(argv[i], "--copyright") == 0 ||
+             strcmp(argv[i], "-c") == 0)
+    {
+      printf(
+        "\ntuxmathadmin version " ADMINVERSION ", Copyright (C) 2007 Tim Holy\n"
+        "This program is free software; you can redistribute it and/or\n"
+        "modify it under the terms of the GNU General Public License\n"
+        "as published by the Free Software Foundation.  See COPYING.txt\n"
+        "\n"
+        "This program is distributed in the hope that it will be useful,\n"
+        "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+        "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
+        "\n");
+      exit(EXIT_SUCCESS);
+    }
+    else if (strcmp(argv[i], "--usage") == 0 ||
+             strcmp(argv[i], "-u") == 0) {
+      usage(0, argv[0]);
+      exit(EXIT_SUCCESS);
+    }
+    else if (strcmp(argv[i], "--path") == 0) {
+      if (i+1 > argc) {
+        fprintf(stderr, "%s option requires an argument (a directory name)\n", argv[i]);
+        usage(EXIT_FAILURE, argv[0]);
+      }
+      else {
+        path = argv[i+1];
+        dir = opendir(path);  // determine whether directory exists
+        if (dir == NULL)
+          error(EXIT_FAILURE,errno,"path:\n  %s",path);
+        closedir(dir);
+        i++; // increment so further processing skips over the argument
+      }
+    }
+    else if (strcmp(argv[i], "--level") == 0) {
+      if (i+1 > argc) {
+        fprintf(stderr, "%s option requires an argument (a level number)\n", argv[i]);
+        usage(EXIT_FAILURE, argv[0]);
+      }
+      else {
+        success = sscanf(argv[i+1],"%d",&level);
+        if (!success) {
+          fprintf(stderr,"level: %s is not a number\n",argv[i+1]);
+          exit(EXIT_FAILURE);
+        }
+        i++; // increment so further processing skips over the argument
+      }
+    }
+    else if (strcmp(argv[i], "--createhomedirs") == 0) {
+      is_creatinghomedirs = 1;
+      if (i+1 > argc) {
+        fprintf(stderr, "%s option requires an argument (a file name)\n", argv[i]);
+        usage(EXIT_FAILURE, argv[0]);
+      }
+      else {
+        file = argv[i+1];
+        fp = fopen(file,"r");   // determine whether the file exists
+        if (fp == NULL)
+          error(EXIT_FAILURE,errno,"createhomedirs using:\n  %s",file);
+        fclose(fp);  // don't read it yet, do that elsewhere
+        i++; // increment so further processing skips over the argument
+      }
+    }
+    else if (strcmp(argv[i], "--confighighscores") == 0) {
+      is_confighighscores = 1;
+    }
+    else if (strcmp(argv[i], "--unconfighighscores") == 0) {
+      is_unconfighighscores = 1;
+    }
+    else if (strcmp(argv[i], "--clearhighscores") == 0) {
+      is_clearinghighscores = 1;
+    }
+    else if (strcmp(argv[i], "--cleargoldstars") == 0) {
+      is_clearinggoldstars = 1;
+    }
+    else if (strcmp(argv[i], "--consolidatelogs") == 0) {
+      is_consolidatinglogs = 1;
+    }
+    else if (strcmp(argv[i], "--clearlogs") == 0) {
+      is_clearinglogs = 1;
+    }
+    else {
+      fprintf(stderr,"Error: option %s not recognized.\n",argv[i]);
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  // All operations require a valid path, so check that now
+  if (path == NULL) {
+    fprintf(stderr,"Must have a valid path (either with --path or in the global configuration)\n");
+    usage(EXIT_FAILURE, argv[0]);
+  }
+
+  // Create homedirs
+  if (is_creatinghomedirs) {
+    if (file == NULL) {
+      fprintf(stderr,"Must specify a filename when creating homedirs\n");
+      usage(EXIT_FAILURE, argv[0]);
+    }
+    create_homedirs(path,file);
+  }
+
+  // Configure high scores
+  if (is_confighighscores) {
+    if (level == 0) {
+      fprintf(stderr,"Must specify a level when configuring highscores\n");
+      usage(EXIT_FAILURE, argv[0]);
+    }
+    config_highscores(path,level);
+  }
+
+  // Unconfigure high scores
+  if (is_unconfighighscores) {
+    unconfig_highscores(path);
+  }
+
+  // Clear high scores
+  if (is_clearinghighscores) {
+    clear_highscores(path);
+  }
+
+  // Clear gold stars
+  if (is_clearinggoldstars) {
+    clear_goldstars(path);
+  }
+   
+  // Consolidate logs
+  if (is_consolidatinglogs) {
+    consolidate_logs(path);
+  }
+
+  // Clear logs
+  if (is_clearinglogs) {
+    clear_logs(path);
+  }
+
+  free(path);
+
+  return EXIT_SUCCESS;
+}
+
+
+void usage(int err, char * cmd)
+{
+  FILE * f;
+
+  if (err == 0)
+    f = stdout;
+  else
+    f = stderr;
+
+  fprintf(f,
+   "\nUsage: %s {--help | --usage | --copyright}\n"
+   "       %s [--path <directory>] --createhomedirs <file>\n"
+   "       %s [--level <levelnum>] --confighighscores\n"
+   "       %s [--path <directory>] [--clearhighscores] [--cleargoldstars] [--consolidatelogs] [--clearlogs]\n"
+    "\n", cmd, cmd, cmd, cmd);
+
+  exit (err);
+}
+
+void display_help(void)
+{
+  printf("\ntuxmathadmin\n"
+         "This program facilitates administering tuxmath, and is particularly\n"
+         "useful for schools and the like that may have many users.\n\n"
+         "Examples:\n"
+         "  tuxmathadmin --path /servervolume/tuxmath_users --createhomedirs users.csv\n"
+         "  tuxmathadmin --createhomedirs users.csv\n"
+         "    Creates a user directory tree in location /servervolume/tuxmath_users,\n"
+         "    according to the structure specified in users.csv.  See configure.pdf\n"
+         "    for details.  The second syntax is applicable if you've defined the\n"
+         "    homedir path in the global configuration file.\n\n"
+         "  tuxmathadmin --confighighscores --level 3\n"
+         "    Sets up sharing of high scores at level 3 of the hierarchy (top is\n"
+         "    level 1).  If students logging in are presented with a choice of grade,\n"
+         "    then classroom, and then user, then level 1 is the school, level 2 is the\n"
+         "    grade, level 3 is the classroom, and level 4 is the individual student.\n"
+         "    So level 3 would set it up so that all kids in the same classroom would\n"
+         "    compete for high scores.\n\n"
+         "  tuxmathadmin --unconfighighscores\n"
+         "    Removes any existing highscores configuration.\n\n"
+         "  tuxmathadmin --clearhighscores\n"
+         "    Clears high scores for all users in the location specified by the homedir\n"
+         "    setting in the global configuration file.\n\n"
+         "  tuxmathadmin --path /servervolume/tuxmath_users/2ndgrade --clearhighscores\n"
+         "    Clears the high scores for all users inside the 2ndgrade hierarchy.\n\n"
+         "  tuxmathadmin --cleargoldstars\n"
+         "    Clears the gold stars for all users.\n\n"
+         "  tuxmathadmin --path /servervolume/tuxmath_users/1st\\ grade/Mrs.\\ Smith --cleargoldstars\n"
+         "    Clears the gold stars for all users in Mrs. Smith's first grade class.\n\n"
+         "  tuxmathadmin --consolidatelogs\n"
+         "    Creates consolidated_log.csv files at one level above the lowest level\n"
+         "    of the directory hierarchy. These files can be opened with a spreadsheet\n"
+         "    program. This may be useful for tracking student progress.\n"
+         "    Note also that each student has a personal log.csv file in his/her own\n"
+         "    directory.\n\n"
+         "  tuxmathadmin --clearlogs\n"
+         "    Deletes all log.csv files in the directory hierarchy.\n\n"
+         );
+}
+
+// This function does the work of creating the user directory tree,
+// given the structure specified in the CSV (comma separated value)
+// file "file".  "path" is the base directory in which this tree is
+// created.
+void create_homedirs(const char *path,const char *file)
+{
+  FILE *fp,*fpue;
+  char buf[PATH_MAX];
+  char *line_begin;
+  char *line_cur;
+  char *line_cur_end;
+  char *copy_start;
+  char fullpath[PATH_MAX];
+  char **current_dirtree = NULL;
+  int current_depth;
+  int max_depth = 0;
+  int this_line_total_depth;
+  int stop_blanking;
+  int i;
+  int len;
+  mode_t mask;
+
+  fp = fopen(file,"r");
+  if (!fp)
+    error(EXIT_FAILURE,errno,"Error: couldn't open:\n  %s for reading",file);
+
+  mask = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH | S_IXUSR | S_IXGRP | S_IXOTH;
+  umask(0x0);  // make dirs read/write for everyone
+  while (fgets (buf, PATH_MAX, fp)) {
+    // Skip leading & trailing whitespace
+    line_begin = eatwhite(buf);
+    // Skip comments
+    if ((*line_begin == ';') || (*line_begin == '#'))
+      continue;
+    // Make sure this line isn't blank
+    if (strlen(line_begin) == 0)
+      continue;
+    //printf("Read the line %s\n",line_begin);
+
+    // Count the number of levels by counting the commas + 1
+    this_line_total_depth = 1;
+    line_cur = line_begin;
+    while (!(*line_cur == '\r' || *line_cur == '\n')) {
+      if (*line_cur == ',')
+        this_line_total_depth++;
+      line_cur++;
+    }
+
+    // If this is our first time, set up the tree structure
+    if (max_depth == 0) {
+      max_depth = this_line_total_depth;
+      current_dirtree = (char **) malloc(max_depth * sizeof(char*));
+      if (current_dirtree == NULL) {
+        fprintf(stderr,"Error: couldn't allocate memory for directory tree.\n");
+        exit(EXIT_FAILURE);
+      }
+      for (i = 0; i < max_depth; i++) {
+        current_dirtree[i] = (char *) malloc(PATH_MAX * sizeof(char));
+        if (current_dirtree[i] == NULL){
+          fprintf(stderr,"Error: couldn't allocate memory for directory tree.\n");
+          exit(EXIT_FAILURE);
+        } else
+          *(current_dirtree[i]) = '\0';  // initialize with blank string
+      }
+    }
+    else {
+      // Check that this line doesn't change the size of the directory hierarchy
+      if (this_line_total_depth != max_depth) {
+        fprintf(stderr,"Error: line\n  '%s'\ncontains a different number of depths to the hierarchy than the previous setting (%d).\n",buf,max_depth);
+        exit(EXIT_FAILURE);
+      }
+    }
+    
+    // Parse the pathname from back to front.  Blank fields at the end
+    // indicate a lack of subdirectories; blank fields at the
+    // beginning indicate that the higher levels of the hierarchy are
+    // not to be changed (just copied "down").  So these have to be
+    // treated differently.
+    *line_cur = '\0';  // replace linefeed with terminal \0
+    line_cur_end = line_cur;
+    current_depth = max_depth-1;
+    stop_blanking = 0;
+    while (current_depth >= 0) {
+      // Back up to the previous comma
+      // Note that line_cur+1 points to the first "real character" of
+      // the string, so don't be bothered that line_cur could get to be
+      // one less than line_begin.
+      while (line_cur >= line_begin && *line_cur != ',')
+        line_cur--;
+      // Determine whether we have a new directory name
+      if (line_cur+1 < line_cur_end) {
+        // We do, copy it over including the terminal \0
+        copy_start = line_cur+1;
+        if (*copy_start == '\"')
+          copy_start++;
+        if (line_cur_end[-1] == '\"') {
+          line_cur_end--;
+          *line_cur_end = '\0';
+        }
+        memcpy(current_dirtree[current_depth],copy_start,line_cur_end-copy_start+1);
+        stop_blanking = 1;  // don't clear blank fields in the future
+      }
+      else {
+        // Blank this particular field, because we don't want old
+        // subdirectories hanging around
+        if (!stop_blanking)
+          *(current_dirtree[current_depth]) = '\0';
+      }
+      current_depth--;
+      if (line_cur >= line_begin)
+        *line_cur = '\0'; // end the processing at the comma
+      line_cur_end = line_cur;
+    }
+
+    // Create the full path
+    strncpy(fullpath, path, PATH_MAX);
+    len = strlen(fullpath);
+    if (fullpath[len-1] != '/' && len+1 < PATH_MAX) {
+      fullpath[len] = '/';  // append a slash, if need be
+      fullpath[len+1] = '\0';
+    }
+    for (i = 0; i < max_depth; i++) {
+      len = strlen(fullpath);
+      strncpy(fullpath+len,current_dirtree[i],PATH_MAX-len);
+      len = strlen(fullpath);
+      if (fullpath[len-1] != '/' && len+1 < PATH_MAX) {
+        fullpath[len] = '/';  // append a slash, if need be
+        fullpath[len+1] = '\0';
+      }
+    }
+
+    // Create the directory
+    if (strlen(fullpath) < PATH_MAX) {
+      if (mkdir(fullpath,mask) < 0) {
+        // There was some kind of error, figure out what happened.
+        // Be a little more verbose than the standard library errors.
+        if (errno == EEXIST) {
+          fprintf(stderr,"Warning: %s already exists, continuing.\n",fullpath);
+        }
+        else if (errno == ENAMETOOLONG) {
+          fprintf(stderr,"Error: the directory name:\n  %s\nwas too long.\n",fullpath);
+          exit(EXIT_FAILURE);
+        }
+        else if (errno == ENOENT) {
+          fprintf(stderr,"Error: One of the upper-level directories in:\n  %s\ndoesn't exist.  Check the syntax of your configuration file.\n",fullpath);
+          exit(EXIT_FAILURE);
+        }
+        else if (errno == ENOSPC) {
+          fprintf(stderr,"Error: the device has no room available.\n");
+          exit(EXIT_FAILURE);
+        }
+        else {
+          // Fall back on the standard library for the remaining error
+          // handling
+          fprintf(stderr,"Error: couldn't make directory %s:\nDo you have write permission for this location?\nDo you need to be root/administrator?\n",fullpath);
+          error(EXIT_FAILURE,errno,"error");
+        }
+      }
+      else {
+        fsync(fileno(stderr));
+        fprintf(stdout,"Creating %s\n",fullpath);
+        fsync(fileno(stdout));
+
+        // Append the name to the user_menu_entries file
+        // First we split off the last item in fullpath
+        line_begin = fullpath;
+        len = strlen(line_begin);
+        line_begin[len-1] = '\0';  // replace terminal '/' with \0
+        line_cur = line_begin + len-1;
+        while (line_cur > line_begin && *line_cur != '/')
+          line_cur--;
+        if (line_cur > line_begin) { // as long as not making in the root directory...a bad idea anyway!
+          *line_cur = '\0';  // Split into two strings
+        }
+        else {
+          line_begin = "/";
+        }
+        line_cur++;   // line_cur now points to beginning of newest directory
+        strncpy(buf,line_begin,PATH_MAX);  // we don't need buf anymore
+        buf[strlen(buf)] = '/';  // append directory separator
+        len = strlen(buf);
+        strncpy(buf+len,USER_MENU_ENTRIES_FILENAME,PATH_MAX-len-strlen(USER_MENU_ENTRIES_FILENAME));
+        // Now do the appending
+        fpue = fopen(buf,"a");
+        if (!fpue) {
+          fprintf(stderr,"Error: can't open file %s for writing.\n",buf);
+          exit(EXIT_FAILURE);
+        }
+        len = fprintf(fpue,"%s\n",line_cur);
+        if (len != strlen(line_cur)+1) {
+          error(EXIT_FAILURE,errno,"Error writing %s to file %s.\n",line_cur,buf);
+        }
+        fclose(fpue);
+      }
+    }
+    else {
+      // The path name was truncated, don't make a corrupt directory
+      fprintf(stderr,"Error: the directory name:\n  %s\nwas too long, quitting.\n",fullpath);
+      exit(EXIT_FAILURE);
+    }
+  }
+  
+  // Free memory
+  for (i = 0; i < max_depth; i++)
+    free(current_dirtree[i]);
+  if (current_dirtree != NULL)
+    free(current_dirtree);
+}
+
+
+// Creates blank highscores files at the specified level of the
+// directory hierarchy.  This will be the level at which highscore
+// competition will occur.
+void config_highscores(const char *path,int level)
+{
+  FILE *fp;
+  char buf[PATH_MAX];
+  int n_dirs;
+  int i;
+  int success;
+
+  n_dirs = directory_crawl(path);
+  success = 0;  // This will change to 1 if we find a directory of the
+                // right level
+  for (i = 0; i < n_dirs; i++) {
+    if (directory_level[i] == level) {
+      // Create a blank highscores file in this directory
+      strncpy(buf,directory[i],PATH_MAX);
+      strncat(buf,HIGHSCORE_FILENAME,PATH_MAX-strlen(buf)-1);
+      if (strlen(buf) >= PATH_MAX-1) {
+        fprintf(stderr,"confighighscores: pathname %s truncated, exiting.\n",buf);
+        exit(EXIT_FAILURE);
+      }
+      fp = fopen(buf,"w");
+      if (!fp)
+        error(EXIT_FAILURE,errno,"confighighscores: file:\n  %s",buf);
+      // That creates a blank file, which is all we have to do
+      fclose(fp);
+      success = 1;
+    }
+  }
+  if (!success) {
+    fprintf(stderr,"Error: no directories of level %d found!",level);
+    exit(EXIT_FAILURE);
+  }
+
+  free_directories(n_dirs);
+}
+
+// Delete all highscores files in the directory hierarchy
+void unconfig_highscores(const char *path)
+{
+  clear_file(path,HIGHSCORE_FILENAME,"unconfighighscores");
+}
+
+// Replaces all highscores files with blank files anywhere in the
+// directory hierarchy.  Replacing it with a blank file, rather than
+// just deleting it, insures that the highscores configuration (in
+// terms of the level at which highscore competition occurs) is not
+// altered.
+void clear_highscores(const char *path)
+{
+  FILE *fp;
+  char buf[PATH_MAX];
+  int n_dirs;
+  int i;
+
+  n_dirs = directory_crawl(path);
+  for (i = 0; i < n_dirs; i++) {
+    // Search for a highscores file in this directory
+    strncpy(buf,directory[i],PATH_MAX);
+    strncat(buf,HIGHSCORE_FILENAME,PATH_MAX-strlen(buf)-1);
+    if (strlen(buf) >= PATH_MAX-1) {
+      fprintf(stderr,"clearhighscores: pathname %s truncated, exiting.\n",buf);
+      exit(EXIT_FAILURE);
+    }
+    fp = fopen(buf,"r");
+    if (fp) {
+      // We found such a file, replace it with a blank one
+      fclose(fp);
+      fp = fopen(buf,"w");
+      if (!fp)
+        error(EXIT_FAILURE,errno,"clearhighscores: file:\n  %s",buf);
+      // That creates a blank file, which is all we have to do
+      fclose(fp);
+    }
+  }
+
+  free_directories(n_dirs);
+}
+
+// Delete all goldstars files in the directory hierarchy
+void clear_goldstars(const char *path)
+{
+  clear_file(path,GOLDSTAR_FILENAME,"cleargoldstars");
+}
+
+// Create consolidated log files at the next-to-lowest level of the
+// hierarchy.  This is basically performing a "cat" operation on all
+// the log.csv files that are below a given point in a directory
+// hierarchy.
+void consolidate_logs(const char *path)
+{
+  int n_dirs;
+  int max_level;
+  int i;
+  int column_names_written = 0;
+  FILE *fplogwrite, *fplogread, *fpusersread;
+  char buf[PATH_MAX], buf2[PATH_MAX], buf3[PATH_MAX];
+  char *line_begin;
+  
+  // Determine the maximum level
+  n_dirs = directory_crawl(path);
+  max_level = 0;
+  for (i = 0; i < n_dirs; i++)
+    if (directory_level[i] > max_level)
+      max_level = directory_level[i];
+
+  // For every directory on the next-to-maximum level,...
+  for (i = 0; i < n_dirs; i++) {
+    if (directory_level[i] != max_level-1)
+      continue;
+
+    // Open the user_menu_entries file, so that we can cycle through
+    // all subdirectories
+    strncpy(buf,directory[i],PATH_MAX);
+    strncat(buf,USER_MENU_ENTRIES_FILENAME,PATH_MAX-strlen(buf)-1);
+    if (strlen(buf) >= PATH_MAX-1) {
+      error(EXIT_FAILURE,0,"consolidatelogs: pathname %s is truncated, exiting.\n",buf);
+    }
+    fpusersread = fopen(buf,"r");
+    if (!fpusersread)
+      error(EXIT_FAILURE,errno,"consolidatelogs: file:\n %s",buf);
+
+    // Create a blank consolidated_log.csv file
+    strncpy(buf,directory[i],PATH_MAX);
+    strncat(buf,"consolidated_log.csv",PATH_MAX-strlen(buf)-1);
+    if (strlen(buf) >= PATH_MAX-1) {
+      error(EXIT_FAILURE,0,"consolidatelogs: pathname %s is truncated, exiting.\n",buf);
+    }
+    fplogwrite = fopen(buf,"w");
+    if (!fplogwrite)
+      error(EXIT_FAILURE,errno,"consolidatelogs: file:\n  %s",buf);
+
+    // Loop over different users
+    while (fgets(buf, PATH_MAX, fpusersread)) {
+      // Skip over white space, and especially blank lines
+      line_begin = eatwhite(buf);
+      if (strlen(line_begin) == 0)
+        continue;
+      // Create the full path & filename of the user's log.csv file
+      strncpy(buf2,directory[i],PATH_MAX);
+      strncat(buf2,line_begin,PATH_MAX-strlen(buf2)-1);
+      strncat(buf2,"/log.csv",PATH_MAX-strlen(buf2)-1);
+      fplogread = fopen(buf2,"r");
+      if (fplogread) {
+        // Copy the relevant lines from the user's log.csv file to the
+        // consolidated log file.  Make sure only one copy of the
+        // column names is written.
+        while(fgets(buf3, PATH_MAX, fplogread)) {
+          line_begin = eatwhite(buf3);
+          if (strlen(line_begin) == 0)
+            continue;
+          if (strncmp(line_begin,"\"User",5) == 0) {
+            if (!column_names_written) {
+              fprintf(fplogwrite,"%s\n",line_begin);
+              column_names_written = 1;
+            }
+          } else {
+            fprintf(fplogwrite,"%s\n",line_begin);
+          }
+        }
+        fclose(fplogread);
+      }
+    }
+    fclose(fpusersread);
+    fclose(fplogwrite);
+  }
+
+  free_directories(n_dirs);
+}
+
+// Delete all log.csv files in the directory hierarchy
+void clear_logs(const char *path)
+{
+  clear_file(path,"log.csv","clearlogs");
+}
+
+// Deletes a named filetype in the directory hierarchy
+void clear_file(const char *path,const char *filename,const char *invoke_name)
+{
+  FILE *fp;
+  char buf[PATH_MAX];
+  int n_dirs;
+  int i;
+
+  n_dirs = directory_crawl(path);
+  for (i = 0; i < n_dirs; i++) {
+    // Search for a goldstars file in this directory
+    strncpy(buf,directory[i],PATH_MAX);
+    strncat(buf,filename,PATH_MAX-strlen(buf)-1);
+    if (strlen(buf) >= PATH_MAX-1) {
+      fprintf(stderr,"%s: pathname %s truncated, exiting.\n",invoke_name,buf);
+      exit(EXIT_FAILURE);
+    }
+    fp = fopen(buf,"r");
+    if (fp != NULL) {
+      // We found such a file, delete it
+      fclose(fp);
+      if (remove(buf) < 0)
+        error(EXIT_FAILURE,errno,"%s: file:\n  %s",invoke_name,buf);
+    }
+  }
+
+  free_directories(n_dirs);
+}
+
+
+// Extracts a single variable from a configuration file and puts the
+// string in the variable "value". Returns 1 on success and 0 on
+// failure.
+int extract_variable(FILE *fp, const char *varname, char** value)
+{
+  char buf[PATH_MAX];
+  char *param_begin;
+  char *tmpvalue;
+
+  rewind(fp);  // start at the beginning of the file
+
+  // Read in a line at a time:
+  while (fgets (buf, PATH_MAX, fp)) {
+    param_begin = buf;
+    // Skip leading whitespace
+    while (isspace(*param_begin))
+      param_begin++;
+    // Skip comments
+    if ((*param_begin == ';') || (*param_begin == '#'))
+      continue;
+    // Test whether it matches the variable name
+    if (strncmp(param_begin,varname,strlen(varname)) == 0) {
+      // Find the "=" sign
+      tmpvalue = strchr(param_begin+strlen(varname), '=');
+      if (tmpvalue == NULL)
+        continue;
+      // Skip over the "=" sign
+      tmpvalue++;
+      // Skip whitespace
+      while (isspace(*tmpvalue))
+        tmpvalue++;
+      // Eliminate any whitespace at end
+      param_begin = tmpvalue;
+      tmpvalue = param_begin + strlen(param_begin) - 1;
+      while (tmpvalue > param_begin && isspace(*tmpvalue)) {
+        *tmpvalue = '\0';
+        tmpvalue--;
+      }
+      // Abort if empty
+      if (strlen(param_begin) == 0)
+        continue;
+      // Successful, copy the result
+      *value = strdup(param_begin);
+      return 1;
+    }
+  }
+  return 0;
+}
+
+// Recursively generates a list of all subdirectories listed in
+// user_menu_entries starting from the given path.  It populates the
+// global variables "directory" and "directory_level", and returns the
+// total number found.  Note this function allocated memory with
+// malloc, so after you're done using these directories you should
+// call free_directories.
+//
+// This function checks to make sure that each directory exists, and
+// exits if not, so you can be sure that all listed directories exist.
+//
+// Note this puts the top level directory (assigned in path) as the
+// first entry (which "main" verifies to be valid), so this function
+// is guaranteed to return at least one directory.
+int directory_crawl(const char *path)
+{
+  int current_length;
+  int previous_length;
+  int current_level;
+  FILE *fp;
+  char buf[PATH_MAX];
+  char fullpath[PATH_MAX];
+  int isdone;
+  int i;
+  char *line_begin;
+  DIR *dir;
+  
+  current_length = 1;
+  directory[0] = (char*) malloc((strlen(path)+2)*sizeof(char));
+  if (directory[0] == NULL) {
+    fprintf(stderr,"Memory allocation error in directory_crawl.\n");
+    exit(EXIT_FAILURE);
+  }
+  strcpy(directory[0],path);
+  // Append '/' if necessary
+  if (directory[0][strlen(path)-1] != '/')
+    strcat(directory[0],"/");
+  current_level = 1;
+  directory_level[0] = current_level;
+
+  isdone = 0;
+  while (!isdone) {
+    previous_length = current_length;
+    isdone = 1;  // We'll be finished if we don't find any new user_menu_entries files
+    for (i = 0; i < previous_length; i++) {
+      // Just parse directories of the most recently-added level
+      // (we've already done the work for previous levels)
+      if (directory_level[i] == current_level) {
+        // Read the user_menu_entries file, if it exists
+        // Note that previous items already have "/" appended, no need
+        // to worry about that here.
+        strncpy(fullpath,directory[i],PATH_MAX);
+        strncat(fullpath,USER_MENU_ENTRIES_FILENAME,PATH_MAX-strlen(fullpath)-1);
+        fp = fopen(fullpath,"r");
+        if (fp != NULL) {
+          // We found the user_menu_entries file, read it and add directories
+          while (fgets (buf, PATH_MAX, fp)) {
+            if (current_length >= MAX_USERS) {
+              fprintf(stderr,"Error: maximum number of users exceeded.");
+              exit(EXIT_FAILURE);
+            }
+            // Skip over leading & trailing white space, and
+            // especially blank lines
+            line_begin = eatwhite(buf);
+            if (strlen(line_begin) == 0)
+              continue;
+
+            directory[current_length] = (char *) malloc((strlen(directory[i])+strlen(line_begin)+2)*sizeof(char));
+            if (directory[current_length] == NULL) {
+              fprintf(stderr,"Memory allocation error in directory_crawl.\n");
+              exit(EXIT_FAILURE);
+            }
+            // Append each new directory to the list
+            strcpy(directory[current_length],directory[i]);
+            strcat(directory[current_length],line_begin);
+            strcat(directory[current_length],"/");
+            directory_level[current_length] = current_level+1;
+            // Check to make sure it's valid
+            dir = opendir(directory[current_length]);
+            if (dir == NULL)
+              error(EXIT_FAILURE,errno,"directory:\n %s",directory[current_length]);
+            closedir(dir);
+            current_length++;
+          }
+          isdone = 0;  // We know we need to check the subdirectories
+          fclose(fp);
+        }  // end of: if (fp != NULL)
+      } // end of: if (directory_level[i] == current_level)
+    } // end of: loop over previous directories
+    current_level++;  // We're all done parsing this level, move on
+  } // end of: while (!isdone)
+  
+  return current_length;
+}
+
+void free_directories(int n)
+{
+  int i;
+
+  for (i = 0; i < n; i++) {
+    free(directory[i]);
+    directory[i] = NULL;
+  }
+}
+
+// Modify a string to eliminate leading and trailing
+// whitespace. Returns a pointer to the first non-space character.
+// Note the input string is modified in-place!
+char* eatwhite(char *buf)
+{
+  char *line_begin, *line_end;
+
+  // Eliminate leading whitespace
+  line_begin = buf;
+  while (isspace(*line_begin))
+    line_begin++;
+  // Eliminate trailing whitespace, especially the \n at the end of the line
+  line_end = line_begin+strlen(line_begin)-1;
+  while (line_end >= line_begin && isspace(*line_end)) {
+    *line_end = '\0';
+    line_end--;
+  }
+
+  return line_begin;
+}

Added: tuxmath/trunk/src/tuxmathrc.rc
===================================================================
--- tuxmath/trunk/src/tuxmathrc.rc	                        (rev 0)
+++ tuxmath/trunk/src/tuxmathrc.rc	2009-02-15 16:45:10 UTC (rev 887)
@@ -0,0 +1,2 @@
+#define TUXMATH_ICON                       104
+TUXMATH_ICON                        ICON "../data/images/tuxmath.ico"




More information about the Tux4kids-commits mailing list