Thread: Contest Results - May 27, 2002

  1. #1
    Just because ygfperson's Avatar
    Join Date
    Jan 2002
    Posts
    2,490

    Contest Results - May 27, 2002

    Sorry these are late... It's hard getting a contest organized and running and stuff...

    I'll begin by posting our respective opinions. (nvoigt, prelude, and me). then comes the code, then comes my last word.


    To a moderator, sticky this, please? For just a couple days. Also, unsticky the Sign Up thread.
    Last edited by ygfperson; 05-27-2002 at 06:45 PM.

  2. #2
    Just because ygfperson's Avatar
    Join Date
    Jan 2002
    Posts
    2,490
    Nvoigt's opinion:
    Cicero:
    speed:

    faster than the user anyway.

    efficiency:

    Surprisingly efficient for such a short program.
    Without any unneccessary stuff, but also lacks some
    parts.

    elegance:

    Logically flowing, yes. Easy to read, no. But maybe it's
    my editor and it looks better in the original one.


    portability:

    Compiles with two warnings, that, if taken seriously, could
    have stopped a bug from creeping in.


    specialized criteria:

    sorting algorithms:

    No sorting.

    interface:

    Simple interface, easy to navigate.

    flexibility:

    Holds 100 records and will crash thereafter.

    Variable and Function naming:

    Okay naming, program is not exactly large enough to see a pattern.

    Standard procedures:

    Standard function calls. No other procedures like algorithms.

    Comments:

    No comments.

    Program structure and modularity:

    Somewhat modular, each interface option is a function.



    Conclusion:

    This program has some highlights and some silly mistakes.
    It is very compact and still functional. It lacks save/load
    options and sorting. It shows some understanding of principles,
    for example the scanf format-string.

    Hard to say, but I'd give it a 3.
    -1 for the bug,
    -0.5 for the 100 records
    -0.5 for not sorting/not saving
    Hammer:
    speed:

    The program is faster than the user anyway.
    Sorting algorithm is not the fastest when
    changing sort order, but this is a personal
    phonebook, not a nationwide address lookup.

    efficiency:

    Efficient, special positive mention for
    delete flags and save-on-demand !


    elegance:

    Source code is broken up into bits that are
    easy to understand, looks neat and is nice to follow.


    portability:

    Compiled with three negligable warnings on VC7.
    I don't have a *nix system at hand, but I didn't
    notice any specialized code.


    specialized criteria:

    sorting algorithms:

    Sorting algorithm is nice for inserting new records
    which probably is the main focus. It's a bit slow on
    reordering large phonebooks, but hopefully people will
    only have a rather small list of phonecontacts ( <100 )
    anyway.

    interface:

    The interface could use some linebreaks so single lines
    and paragraphs are easier to make out, but for an interface
    using only a commandline, it's as good as it gets.

    flexibility:

    Data structures can handle any amount of records. Structures themselves are fixed as in any database, for easier access on disc. Field lengths are easily changeable by changing the defines on top and recompile.

    Variable and Function naming:

    Good naming, conventions for function naming are clear and helpful.

    Standard procedures:

    Standard list procedures, standard sorting procedure.

    Comments:

    Good comments, light reading.

    Program structure and modularity:

    Code is seperated into 4 modules and main.
    To make it easier in large projects, each
    module should have it's own header. This way,
    you don't force a full recompile of the whole
    program when one prototype changes.



    Conclusion:

    Also the above may seem a bit negative at times, it's only because I won't mention all those positive points. Just look at it yourself. It's a great project, well done and well documented. If we had a smaller scale, I would probably deduct some points for the things mentioned above, so it might come out a 97/100, but on a scale of 5 to 1 I think it's a full 5.

  3. #3
    Just because ygfperson's Avatar
    Join Date
    Jan 2002
    Posts
    2,490
    Prelude's opinion:
    Cicero:
    Efficiency: 3*
    Elegance: 1*
    Portability: 2*
    Sorting: 0*
    Interface: 3*
    Flexibility: 1*


    Efficiency Notes:
    This program is efficient because it sacrifices needed features. A random
    access array of structs with a fixed length is used and the rest of the
    program is what I would consider bare bones. It stores the input in a list
    and allows minimal processing on that input for one run of the program. The
    data is not saved and cannot be updated, the four options for maintaining
    the list are add, remove, search, and print all. But even so, the program is
    efficient and I can't cut points in this category for lack of features. I
    did cut several points for the searching routines though since they do not
    have an exit case and will iterate to the very end every time, even if the
    field being searched for is the first one.

    Elegance Notes:
    Just about everything in this program is unsafe, from the use of gets to the
    complete lack of error checking and input validation. The program also
    breaks when the user attempts to search for a record or remove a record. The
    memory allocation is truly nasty and it isn't freed with code. There's also
    a problem when adding to an index in the array that was previously deleted.
    The program deletes data from the array by clearing it out and changing the
    name field to "empty". In the add_a_number function the array is searched
    for the first empty field whether it actually be empty or simply deleted. If
    it finds a deleted field the new record is placed there, but once the data
    is input the function doesn't return and moves on to the situation where the
    "empty" field was not found and another record must be input.

    Portability Notes:
    I question the portability of this program mostly because it is broken.
    While it doesn't use any terribly nonstandard constructs, it's a toss-up
    whether or not the program will run correctly on any platform due to the
    errors. The actual code is standard and will compile, but I cut several
    points because it is almost guaranteed to break on any platform, thus making
    it completely nonportable. Once the errors were fixed the program worked
    correctly, but I can't judge based on my changes.

    Sorting Notes:
    This program doesn't sort at all.

    Interface Notes:
    The interface isn't bad, but it could use a few more options and further
    explanation for usage. Perhaps a help option that explains everything to the
    user.

    Flexibility Notes:
    There is no flexibility in the number of records allowed. One may consider
    100 records to be sufficient, but any well written program will be able to
    handle that one user who has 50000 phonebook entries, even if everyone else
    has 20. This entry uses dynamic allocation for each record, the concept is
    excellent but the implementation falls far short of what I would expect in
    any real program. For the dynamic allocation of input I gave an extra point,
    but took away all of the others for the rest of the program.

    Extra Comments
    --------------
    Variable and Function naming:
    The use of the 'total' variable for maintaining the number of current
    records is wrong since the implementation reserves identifiers starting with
    'to' in lower case.

    Program Comments:
    There are no comments.

    Personal Comments
    -----------------
    This entry is not one that I would use for my phonebook. When the errors
    were fixed the program ran correctly, but it still lacks both useful and
    necessary features.


    -Prelude
    Hammer:
    Efficiency: 4*
    Elegance: 3*
    Portability: 3*
    Sorting: 4*
    Interface: 5
    Flexibility: 3*


    Efficiency:
    I have a bit of a problem with the choice of a linked list
    for the data structure since a large number of records would result in a
    considerable slow down. I personally would have used either a binary tree or
    hashed table to speed the program up with larger numbers of records. This is
    perhaps the biggest flaw of the entry.

    Elegance Note:
    While the code itself is very well written and clean, I have to cut a few
    points on elegance due to the massive number of valid Lint warnings (264
    Total). With minor changes in code structure most of these warnings could be
    silenced.

    Portability Note:
    The use of fread and fwrite minimizes portability across different machines
    if the data file is created on another system. While this doesn't seem
    likely, it is a potential problem which would require another program to
    translate between systems or convert the data file to text.

    Sorting Algorithm Note:
    In my opinion the choice of algorithm is a wise one as long as the number of
    items in the list doesn't exceed a great number such as 1 million. This
    doesn't seem likely so the algorithm is well suited to the data.

    Flexibility Note:
    I found the size limit of the data fields in the Record structure very
    restrictive. While most input will be within the ranges chosen, some may not
    and dynamic allocation would have been both more flexible for the user
    and space saving in the data file.

    Extra Comments
    --------------
    Variable and Function naming:
    #ifndef __PHONE__ - Identifiers with leading underscores are reserved by the
    implementation.

    Personal Comments
    -----------------
    All in all this is an excellent program and most of my concerns are
    nitpicking on unlikely situations. The numberof user options gives me the
    sense that the programmer was doing more than just meeting a set of
    requirements and if there were a judging criteria for going the extra mile
    this entry would get a 5. I'm very impressed, well done.


    -Prelude

  4. #4
    Just because ygfperson's Avatar
    Join Date
    Jan 2002
    Posts
    2,490
    ygfperson's opinion:

    cicero's:
    It looks really simple and elegant. Unfortunately, it stalls at places where you have to type into a prompt. (other than in adding names and numbers to the list). It does the bare minimum: store, retrieve, delete. (And of course, exit.)

    efficiency: very efficient. 5
    elegance: mostly elegant: 4.
    portability: portable. 5
    sorting algorithms: none - 0
    interface: bare minimum, yet it works... except for the crashing. 2
    flexibility: little flexibility. Mostly everything is set into predefined blocks. While a normal person doesn't have a name or phone number that exceeds 100 characters, he/she may have 100+ names to enter into the database. However, this program is modular. Its simplicity and size make it easy to maintain. 3
    other comments: this is sort of what I was looking for. Something short and to the point. However, this sacrifices much, like sorting, to achieve that.
    hammer's:
    efficiency: this program isn't lean but it's still mean. However, it doesn't bogart resources, it uses them properly. 4
    elegance: very elegant. The only improvement I could see was to use C++. (But obviously he couldn't do that.) 5
    portability: portable. 5
    sorting algorithms: It looks like he used a bubble sort. It trades speed for code simplicity. However, on a scale like this, speed doesn't matter. I guess he could have done better... but on a simple contest like this? He doesn't need to. And then there's the linked list problem Prelude talked about. 4
    Interface: very nice. Nested menus and everything. 5
    Flexibility: while there are still limits on the names and phone numbers, the number of names and phone numbers is unlimited. (theoretically). It is also very modular and can probably be maintained easily. 5
    Other comments: he really went out of his way to think out every possible feature. I expected something less for a mere phone book. But I guess I can settle for it. The only annoying thing about this program I found was the address. 5 lines for an address? I guess somebody in Tuxemencalitaappilatioanaville, Mississippi might have use for that. But that's merely a small detail. It's excellent.

  5. #5
    Just because ygfperson's Avatar
    Join Date
    Jan 2002
    Posts
    2,490
    cicero:
    Code:
    #include     <stdio.h>
    #include     <stdlib.h>
    #include     <string.h>
    int total=0;
    struct
        {
        char*name;
        char*number;
        }list[100];
    void add_a_number()
        {
        int i;
        for(i=0;i<total;i++)
            {
            if(!strcmp(list[i].name,"empty"))
                {
                printf("Type a name:");
                gets((list[i].name=calloc(100,sizeof(char))));
                printf("Type a phone number:");
                gets((list[i].number=calloc(100,sizeof(char))));
                }
            }
        printf("Type a name:");
        gets((list[total].name=calloc(100,sizeof(char))));
        printf("Type a phone number:");
        gets((list[total].number=calloc(100,sizeof(char))));
        total++;
        }
    void remove_a_number()
        {
        int i;
        char*find;
        printf("Type a name to remove:");
        gets(find);
        for(i=0;i<total;i++)
            {
            if(!strcmp(find,list[i].name))
                {
                strcpy(list[i].name,"empty");
                strcpy(list[i].number,"empty");
                }
            }
        }
    void find_a_number()
        {
        int i;
        char*find;
        printf("Type a name to find:");
        gets(find);
        for(i=0;i<total;i++)
            {
            if(!strcmp(find,list[i].name))
                {
                printf("Item#%d\n",i);
                printf("------\n");
                printf("Name:%s\n",list[i].name);
                printf("Number:%s\n",list[i].number);
                printf("\n\n");
                }
            }
        }
    void list_all_numbers()
        {
        int i;
        for(i=0;i<total;i++)
            {
            printf("Item#%d\n",i);
            printf("------\n");
            printf("Name:%s\n",list[i].name);
            printf("Number:%s\n",list[i].number);
            printf("\n\n");
            }
        }
    int main()
        {
        char opt;
        while(1)
            {
            printf("\t\t\tGod's Phonebook\n");
            printf("\t\t\t---------------\n");
            printf("\t\t\t1<- Add a number\n");
            printf("\t\t\t2<- Remove a number\n");
            printf("\t\t\t3<- Find a number\n");
            printf("\t\t\t4<- List all numbers\n");
            printf("\t\t\t5<- Quit\n");
            scanf("%1[12345]%*c",&opt);
            switch(opt)
                {
                case'1':
                    add_a_number();
                    break;
                case'2':
                    remove_a_number();
                    break;
                case'3':
                    find_a_number();
                    break;
                case'4':
                    list_all_numbers();
                    break;
                case'5':
                    exit(0);
                    break;
                }
            }
        }

  6. #6
    Just because ygfperson's Avatar
    Join Date
    Jan 2002
    Posts
    2,490
    Hammer's:
    ioUtilities.c:
    Code:
    #include "main.h"
    
    /*
     ===============================================================================
     	Author:		Hammer, May 2002, For www.cprogramming.com/cboard/
     	File:		ioUtilities.c
     	Contents:	io_GetInt
    				io_EnterToContinue
    				io_GetYesNo
    				io_GetLine
    				io_WriteAllRecordsToFile
    				io_WriteRecordToFile
    				io_LoadAppConfig
    				io_SaveAppConfig
     ===============================================================================
    */
    
    static FILE *fp;
    
    /*
     ===============================================================================
     	Function: 	ioLoadPhoneBookFromFile
        Args: 		Pointer to the node list 
        Returns: 	Pointer to the node list.
        			NULL
        Purpose:	Loads the Record structs from the phone book file into the List
     ===============================================================================
    */
    struct Node *io_LoadPhoneBookFromFile(struct Node *List)
    {
    	struct Record	rec, *recptr;
    	struct Node		*tmpListPtr;
    
    	if ((fp = fopen(STR_FILENAME, "rb")) == NULL)
    	{	/* Failed to open database file */
    		printf("ERROR: Unable to open phone book file: %s\n", STR_FILENAME);
    		return(List);
    	}
    
    	if (List)
    	{	/* List not empty, kill it before we start reloading */
    		(void)li_Traverse(List, free);
    		List = li_Destroy(List);
    	}
    
    	while (fread(&rec, sizeof(struct Record), 1, fp) == 1)
    	{
    		if ((recptr = malloc(sizeof(struct Record))) == NULL)
    		{					/* Memory failure, don't do anymore */
    			perror("malloc");
    			break;
    		}
    
    		*recptr = rec;
    
    		if ((tmpListPtr = li_Insert(List, recptr, rec_Compare)) == NULL)
    		{
    			printf("Error performing insert on list.  Item not addded.\n");
    			free(recptr);	/* throw record away */
    		}
    		else
    		{
    			List = tmpListPtr;
    			recptr->ID = gl_HighestID;
    			recptr->Status = (unsigned char)0;
    		}
    	}
    
    	(void)fclose(fp);
    	gl_AppData.DataChanged = FALSE;
    	return(List);
    }
    
    /*
     ===============================================================================
        Function: 	io_GetInt
        Args:		maximum number for user to choose
        Returns: 	Number chosen, or -1 if x or X entered (for menu use)
        Notes:		Assumes that 0 will never be a valid option, and will ask
        			the user to re-enter.
        Purpose:	Get an int from stdin (keyboard).
     ===============================================================================
     */
    int io_GetInt(int Max)
    {
    	int		Input, len;
    	char	buffer[BUFSIZ];
    
    	for (;;)
    	{
    		if ((len = io_GetLine(buffer, BUFSIZ, stdin)) != 0)
    		{
    			if (len == 1 && (buffer[0] == 'x' || buffer[0] == 'X'))
    			{	/* Allow for xX to be entered, and return -1 */
    				Input = RC_BAD;
    				break;
    			}
    
    			if ((Input = atoi(buffer)) != 0)
    			{
    				if (Input >= 1 && Input <= Max)
    				{
    					break;
    				}
    			}
    		}
    
    		printf("Invalid. Try again (1-%d, x to exit)>", Max);
    	}
    
    	return(Input);
    }
    
    /*
     ===============================================================================
        Function: 	io_EnterToContinue
        Args: 		None
        Returns: 	None
        Purpose:	Simulates the good old DOS PAUSE
     ===============================================================================
     */
    void io_EnterToContinue(void)
    {
    	printf("---> Enter To Continue <---");
    	FLUSH_INPUT;
    }
    
    /*
     ===============================================================================
        Function: 	io_GetYesNo
        Args: 		String containing the prompt to be displayed
        Returns: 	TRUE if yY entered, FALSE if nN entered
        Purpose:	Get a Y or N from stdin (keyboard)
     ===============================================================================
     */
    bool_t io_GetYesNo(char *prompt)
    {
    	int		c;
    	bool_t	rc;
    
    	for (;;)
    	{
    		printf("%s", prompt);
    		c = getchar();
    		switch (c)
    		{
    		case 'y':
    		case 'Y':
    			rc = TRUE;
    			break;
    		case 'n':
    		case 'N':
    			rc = FALSE;
    			break;
    		default:
    			if (c != '\n') FLUSH_INPUT;
    			continue;
    		}
    		break;
    	}
    
    	if (c != '\n') FLUSH_INPUT;
    	return(rc);
    }
    
    /*
     ===============================================================================
        Function: 	io_GetLine
        Args: 		buffer to place the text
        			length of the buffer
        			file pointer to read from.
        Returns: 	Chars read and placed into buffer
        Purpose:	Same as fgets() but does not keep the newline character
     ===============================================================================
     */
    int io_GetLine(char *buffer, int maxlen, FILE *fp)
    {
    	int		len = 0, c;
    	char	*ptr = buffer;
    	char	*endptr = buffer + maxlen - 1;
    
    	if (fp != NULL)
    	{
    		while ((c = fgetc(fp)) != EOF)
    		{
    			if (c == '\n') break;
    			*ptr = c;
    			ptr++;
    			len++;
    			if (ptr == endptr) break;
    		}
    	}
    
    	*ptr = '\0';
    	return(len);
    }
    
    /*
     ===============================================================================
        Function: 	io_WriteAllRecordsToFile
        Args: 		Pointer to list 
        Returns: 	Number of items written.
        Purpose:	Save all Record structs in the List to a disk file
     ===============================================================================
     */
    int io_WriteAllRecordsToFile(struct Node *List)
    {
    	unsigned char	status = ST_DELETED;
    
    	if ((fp = fopen(STR_FILENAME, "wb")) == NULL)
    	{	/* Failed to open database file */
    		printf("ERROR: Unable to open phone book file: %s\n", STR_FILENAME);
    		return(0);
    	}
    
    	/* First delete dead records from the list, then write them to disk */
    	gl_AppData.MainList = li_DeleteNodeAndData(gl_AppData.MainList, &status, rec_CompareStatus);
    	(void)li_Traverse(List, io_WriteRecordToFile);
    
    	(void)fclose(fp);
    
    	gl_AppData.DataChanged = FALSE;
    	return(gl_HighestID);
    }
    
    /*
     ===============================================================================
        Function: 	io_WriteRecordToFile
        Args: 		Pointer to record structure to be written.
        Returns: 	Nothing
        Purpose:	Sub-function to write a single Record struct to disk
     ===============================================================================
     */
    void io_WriteRecordToFile(void *ptr)
    {
    	struct Record	*rec = ptr;
    
    	if (rec->Status & ST_DELETED) return;	/* Don't save deleted records */
    
    	if (fp == NULL)
    	{
    		fprintf(stderr, "ERROR: Attempting to write to unopened file\n");
    		return;
    	}
    
    	if (fwrite(rec, sizeof(struct Record), 1, fp) != 1) perror("WriteRecordToFile");
    }
    
    /*
     ===============================================================================
        Function: 	io_LoadAppConfig
        Args: 		None
        Returns: 	Nothing
        Purpose:	Loads the appconfig structure from disk
     ===============================================================================
     */
    void io_LoadAppConfig(void)
    {
    	FILE				*myfp;
    	struct appconfig	tmpcfg;
    
    	if ((myfp = fopen(STR_CFG_FILENAME, "rb")) == NULL) return;
    	if (fread(&tmpcfg, sizeof(struct appconfig), 1, myfp) == 1) gl_AppCfg = tmpcfg;
    
    	(void)fclose(myfp);
    }
    
    /*
     ===============================================================================
        Function: 	io_SaveAppConfig
        Args: 		None
        Returns: 	Nothing
        Purpose:	Saves the appconfig structure to disk
     ===============================================================================
     */
    void io_SaveAppConfig(void)
    {
    	FILE	*myfp;
    
    	if ((myfp = fopen(STR_CFG_FILENAME, "wb")) == NULL) return;
    
    	if (fwrite(&gl_AppCfg, sizeof(struct appconfig), 1, myfp) != 1)
    		perror (STR_CFG_FILENAME);
    
    	(void)fclose(myfp);
    }
    ListUtilities.c
    Code:
    #include "main.h"
    
    /*
     ===============================================================================
     	Author:		Hammer, May 2002, For www.cprogramming.com/cboard/
     	File:		ListUtilities.c
     	Contents:	li_Create
     				nd_Create
     				nd_Destroy
     				li_Traverse
     				li_Insert
     				li_Destroy
     				li_GetDataPtrByID
     				li_DeleteNodeAndData
     				li_Count
     ===============================================================================
    */
    
    /*
     ===============================================================================
        Function: 	li_Create
        Args: 		None
        Returns: 	NULL pointer.
        Purpose:	Dummy function to start off a list.
     ===============================================================================
     */
    struct Node *li_Create(void)
    {
    	return (NULL);
    }
     
    /*
     ===============================================================================
        Function: 	nd_Create
        Args: 		A void ptr to the data to be referenced (A record).
        Returns: 	Pointer to new node, ready for insertion into the tree.
        			NULL if malloc fails
        Purpose:	Creates a single node, containing a pointer to some data (Record)
     ===============================================================================
     */
    struct Node *nd_Create(void *ptr, struct Node *NextNode)
    {
    	struct Node *newptr;
    
    	if ((newptr = malloc(sizeof(struct Node))) == NULL)
    	{
    		perror("nd_create, malloc");
    		return (NULL);
    	}
    
    	newptr->DataPtr = ptr;
    	newptr->Next = NextNode;
    	return(newptr);
    }
    
    /*
     ===============================================================================
        Function: 	nd_Destroy
        Args: 		A pointer to a node to be free()'d
        Returns: 	Nothing
        Purpose:	The data the node points to is not freed by the function, 
        			only the node itself is.
     ===============================================================================
     */
    void nd_Destroy(struct Node *ptr)
    {
    	free(ptr);
    }
    
    /*
     ===============================================================================
        Function: 	li_Traverse
        Args: 		A pointer to the first node in the list
        			A pointer to a function used to process each node.
        Returns: 	Number of Nodes processed
        Purpose:	Traverse the list, sending each nodes data to a function
     ===============================================================================
     */
    int li_Traverse(struct Node *List, FPTR_Action fptr_Action)
    {
    	struct Node *Current = List;
    	int Count = 0;
    	
    	while (Current)
    	{
    		fptr_Action(Current->DataPtr);
    		Count++;
    		Current = Current->Next;
    	}
    	return (Count);
    }
    /*
     ===============================================================================
        Function: 	li_Insert
        Args: 		A pointer to the first node in the list
        			A pointer to the data to be referenced by the node
        			A pointer to a function to be used for comparison.
        Returns: 	Pointer to the first node in the list
        			NULL is nd_Create fails.
        Notes:		The caller should use caution to ensure the List pointer
        			is not lost if this function returns NULL.
        Purpose:	Inserts a new node into the list in the correct sorted order.
     ===============================================================================
     */
    struct Node *li_Insert(struct Node *List, void *NewData, FPTR_Compare fptr_Compare)
    {
    	struct Node *newptr;
    	struct Node *Current = List;
    	struct Node *Previous = NULL;
    	
    	while (Current)
    	{
    		if (fptr_Compare(Current->DataPtr, NewData) > 0)
    			break; /* Found the entry point */
    		Previous = Current; 
    		Current = Current->Next;
    	}
    	
    	if ((newptr = nd_Create(NewData, Current)) == NULL)
    	{	/* malloc failure in nd_Create */
    		return (NULL);
    	}
    	
    	if (Previous == NULL)
    		List = newptr;
    	else
    		Previous->Next = newptr;
    	
    	gl_HighestID++;
    	return (List);
    }
    
    /*
     ===============================================================================
        Function: 	li_Destroy
        Args: 		A pointer to the first node in the list
        Returns: 	Nothing
        Purpose:	This runs through the list, destroying nodes.  It does
        			not free the data that each node points to.
     ===============================================================================
     */
    struct Node *li_Destroy(struct Node *List)
    {
    	struct Node *Current = List;
    	struct Node *tmp;
    	
    	while (Current)
    	{
    		tmp = Current->Next;
    		nd_Destroy(Current);
    		Current = tmp;
    	}
    	gl_HighestID = 0;
    	
    	return (NULL);
    }
    
    /*
     ===============================================================================
        Function: 	li_GetDataPtrByID
        Args: 		A pointer to the first node in the list
        			An int for the ID field.
        Returns: 	Pointer to a Record struct, allowing direct access by the caller
        Purpose:	Gets the address of a Record structure, as *owned* by the list.
     ===============================================================================
    */
    struct Record *li_GetDataPtrByID(struct Node *List, int ID)
    {
    	struct Node *Current = List;
    	struct Record *rec;
    	
    	while (Current)
    	{
    		rec = Current->DataPtr;
    		if (rec->ID == ID)
    			break;
    		Current = Current->Next;
    	}
    	return ((Current)?Current->DataPtr:NULL);
    }
    
    /*
     ===============================================================================
        Function: 	li_DeleteNodeAndData
        Args: 		A pointer to the first node in the list
        			A pointer to the data to be referenced by the node
        			A pointer to a function to be used for comparison.
        Returns: 	Pointer to the first node in the list
        			NULL is nd_Create fails.
        Notes:		The caller should use caution to ensure the List pointer
        			is not lost if this function returns NULL.
        Purpose:	As the function name says!
     ===============================================================================
     */
    struct Node *li_DeleteNodeAndData(struct Node *List, void *CompareData, FPTR_CompareDelete fptr_Compare)
    {
    	struct Node *Current = List;
    	struct Node *Previous = NULL;
    	struct Node *tmp;
    	
    	while (Current)
    	{
    		if (fptr_Compare(Current->DataPtr, CompareData) > 0)
    		{	/* Found a node to remove */
    			if (Current == List) List = Current->Next;
    			tmp = Current->Next;
    			if (Previous)
    			{
    				Previous->Next = tmp;
    			}
    			free (Current->DataPtr);
    			free (Current);
    			Current = tmp;
    		}
    		else
    		{	/* No match, move on */
    			Previous = Current; 
    			Current = Current->Next;
    		}
    	}
    	
    	return (List);
    }
    
    /*
     ===============================================================================
        Function: 	li_Count
        Args: 		A pointer to the first node in the list
        Returns: 	Number of Nodes in list
        Purpose:	Counts the nodes.
     ===============================================================================
     */
    int li_Count(struct Node *List)
    {
    	struct Node *Current = List;
    	int Count = 0;
    	
    	while (Current)
    	{
    		Count++;
    		Current = Current->Next;
    	}
    	return (Count);
    }
    
    /*
     ===============================================================================
        Function: 	li_Sort
        Args: 		A pointer to the first node in the list
        			Function pointer used for comparisons
        Returns: 	None
        Purpose:	Sorts the nodes, using the given function.
     ===============================================================================
     */
    void li_Sort(struct Node *List,  FPTR_Compare fptr_Compare)
    {
    	struct Node *Current, *Next;
    	void *Temp;
    	bool_t StillDoingSwaps = TRUE;
    	
    	while (StillDoingSwaps == TRUE)
    	{
    		StillDoingSwaps = FALSE;
    		Current = List;
    		while (Current)
    		{
    			if ((Next = Current->Next) != NULL)
    			{
    				if (fptr_Compare (Current->DataPtr, Next->DataPtr) > 0)
    				{
    					Temp = Current->DataPtr;
    					Current->DataPtr = Next->DataPtr;
    					Next->DataPtr = Temp;
    					StillDoingSwaps = TRUE;
    				}
    			}
    			Current = Next;
    		}
    	}
    }
    main.c
    Code:
    #include "main.h"
    
    /*
     ===============================================================================
     	Author:		Hammer, May 2002, For www.cprogramming.com/cboard/
     	File:		main.c
     	Contents:	Global variables
     				main
     ===============================================================================
    */
    
    /*
     ===============================================================================
     	The following are the only global variables
     ===============================================================================
    */
    int 	gl_HighestID;
    struct 	appdata gl_AppData;
    struct 	appconfig gl_AppCfg;
    
    /*
     ===============================================================================
        Function: 	main
        Args: 		From command line.  
        			- This can be empty.  In this case an interactive menu is loaded.
        			- or if present, it is taken to represent search criteria.  
        			In this case, the database is loaded, searched and the 
        			relevant records displayed, then the application terminates.
        Returns: 	An int of course! (Always 0)     Don't void your main!  
     ===============================================================================
     */
    int main(int argc, char *argv[])
    {	
    	struct Node *tmpListPtr;
    	int i;
    
    	/*
    	 * Load and setup app config
    	 */
    	memset (&gl_AppData, 0, sizeof(struct appdata));
    	memset (&gl_AppCfg, 0, sizeof(struct appconfig));
    	
    	io_LoadAppConfig();
    	
    	if (gl_AppCfg.LinesPerDisplay <= 0 || gl_AppCfg.LinesPerDisplay > MAX_LINES_PER_PAGE)
    		gl_AppCfg.LinesPerDisplay = DEF_LINES_PER_PAGE;
    	
    	/*
    	 * Create and load the link list of Record structs
    	 */
    	gl_AppData.MainList = li_Create();	
    	
    	if ((tmpListPtr = io_LoadPhoneBookFromFile(gl_AppData.MainList)) == NULL)
    		printf("No data loaded from phone book.\n");
    	else gl_AppData.MainList = tmpListPtr;
    	
    	/*
    	 * If we have command line args, use them as search criteria, then terminate
    	 */
    	if (argc > 1)
    	{
    		gl_AppData.SearchField = RECF_NAME;
    		for (i = 1; i < argc; i++)
    		{
    			strncpy(gl_AppData.SearchText, argv[i], MAXL_NAME+1);
    			(void)li_Traverse (gl_AppData.MainList, rec_SearchAndPrint);
    		}
    		(void)li_Traverse (gl_AppData.MainList, free);
    		(void)li_Destroy (gl_AppData.MainList);
    		return (0);
    	}
    	
    	/*
    	 * Start interactive session, go into menu display until the user exits
    	 */
    	printf ("---> Auto-Save is %s <---\n", (gl_AppCfg.AutoSave)?"On":"Off");
    	ui_DisplayMenu(MainMenu);
    	
    	/*
    	 * Check if we need to save changes, then destory the memory we alloc'd earlier
    	 */
    	if (gl_AppData.DataChanged)
    	{
    		ui_Save(NULL);
    	}
    	
    	(void)li_Traverse (gl_AppData.MainList, free);
    	
    	(void)li_Destroy (gl_AppData.MainList);
    	
    	/*
    	 * Finally save the app config for next time
    	 */
    	io_SaveAppConfig();
    	
    	return (0);
    }
    RecordUtilities.c
    Code:
    #include "main.h"
    
    /*
     ===============================================================================
     	Author:		Hammer, May 2002, For www.cprogramming.com/cboard/
     	File:		RecordUtilities.c
     	Contents:	rec_Compare
    				rec_CompareID
    				rec_CompareStatus
    				rec_PrintShort
    				rec_PrintLong
    				rec_SearchAndPrint
     ===============================================================================
    */
    
    /*
     ===============================================================================
        Function: 	rec_Compare
        Args: 		2 pointers to Record struct's to be compared
        Returns: 	<0 if p1 < p2
        			>0 if p1 > p2
        			 0 if p1 = p2
        Purpose:	Compares 2 Record struct's by the element specified in gl_AppCfg.SortField
     ===============================================================================
     */
    int rec_Compare(const struct Record *rec1, const struct Record *rec2)
    {
    	char Fields[2][MAXL_FIELD+1];
    	int len[2], i, j;
    	
    	/*
    	 * Set up strings for case insensative compare
    	 */
    	if (gl_AppCfg.SortField == RECF_ID)
    		return ((rec1->ID > rec2->ID));
    
    	strncpy (Fields[0], rec_GetFieldPointer(rec1, gl_AppCfg.SortField), MAXL_FIELD+1); 
    	strncpy (Fields[1], rec_GetFieldPointer(rec2, gl_AppCfg.SortField), MAXL_FIELD+1);
    	len[0] = strlen(Fields[0]); 
    	len[1] = strlen(Fields[1]);
    
    	for (i = 0; i < 2; i++)
    	{
    		for (j = 0; j < len[i]; j++)
    		{
    			if (isupper(Fields[i][j])) Fields[i][j] = tolower(Fields[i][j]);
    		}
    	}
    	
    	return (strcmp(Fields[0], Fields[1]));
    }
    
    /*
     ===============================================================================
        Function: 	rec_CompareID
        Args: 		Pointer to Record struct's to be compared with ID
        Returns: 	>0 if match
        			 0 if no match
        Purpose:	Compares 2 Record structs by the ID element
     ===============================================================================
     */
    int rec_CompareID(const struct Record *rec, void *ptr)
    {
    	int *idptr = ptr;
    	
    	if (rec->ID == *idptr)
    		return (1);
    	else return (0);
    }
    
    /*
     ===============================================================================
        Function: 	rec_CompareStatus
        Args: 		Pointer to Record struct's to be compared with Status field
        Returns: 	>0 if match
        			 0 if no match
        Purpose:	Compares 2 Record structs by the Status element
     ===============================================================================
     */
    int rec_CompareStatus(const struct Record *rec, void *ptr)
    {
    	unsigned char *statusptr = ptr;
    	
    	if (rec->Status & *statusptr) /* bitwise comparison */
    		return (1);
    	else return (0);
    }
    /*
     ===============================================================================
        Function: 	rec_PrintShort
        Args: 		A pointer to a Record struct to be printed
        Returns: 	Nothing
        Purpose:	Print only a couple of fields from a single Record struct
     ===============================================================================
     */
    void rec_PrintShort(void *ptr)
    {
    	struct Record *rec = ptr;
    	printf("%s%3d> %-20s %s %s\n",(rec->Status & ST_DELETED)?"Deleted>":"",  rec->ID, rec->Name, rec->PhoneNum1, rec->EmailAddr);
    }
    
    /*
     ===============================================================================
        Function: 	rec_PrintLong
        Args: 		A pointer to a Record struct to be printed
        Returns: 	Nothing
        Purpose:	Prints all fields from a single Record struct
     ===============================================================================
     */
    void rec_PrintLong(void *ptr)
    {
    	struct Record *rec = ptr;
    	
    	printf ("Record ID:\t%d %s\n", rec->ID, (rec->Status & ST_DELETED)?"(Deleted)":"");
    	printf ("Name:\t\t%s\n", rec->Name);
    	printf ("1st Phone:\t%s\n", rec->PhoneNum1);
    	printf ("2nd Phone:\t%s\n", rec->PhoneNum2);
    	printf ("3rd Phone:\t%s\n", rec->PhoneNum3);
    	printf ("Address:\t%s\n", rec->AddrLine1);
    	if (rec->AddrLine2[0] != '\0') printf ("        \t%s\n", rec->AddrLine2);
    	if (rec->AddrLine3[0] != '\0') printf ("        \t%s\n", rec->AddrLine3);
    	if (rec->AddrLine4[0] != '\0') printf ("        \t%s\n", rec->AddrLine4);
    	if (rec->AddrLine5[0] != '\0') printf ("        \t%s\n", rec->AddrLine5);
    	printf ("Email Addr:\t%s\n", rec->EmailAddr);
    	printf ("Misc Data:\t%s\n", rec->Misc);
    }
    
    /*
     ===============================================================================
        Function: 	rec_SearchAndPrint
        Args: 		A pointer to a Record struct to be printed (if criteria meet)
        Returns: 	Nothing
        Purpose:	Called during the list traversal process, this function will
        			search a specified field for the search string stored in the
        			global struct gl_AppData.SearchText
     ===============================================================================
     */
    void rec_SearchAndPrint(void *ptr)
    {
    	struct Record *rec = ptr;
    	char *sptr, *tmp;
    	int len, i;
    	char rectext[MAXL_FIELD+1];
    	
    	if (gl_AppData.SearchField == RECF_DUMMY)
    		sptr = NULL;
    	else sptr = rec_GetFieldPointer(rec, gl_AppData.SearchField);
    	
    	if (sptr)
    	{
    		strncpy(rectext, sptr, MAXL_FIELD+1);
    
    		len = strlen(rectext);
    		/* Convert data to lower case to allow for case insensative search */
    		for (i = 0, tmp = rectext; i < len; i++, tmp++)
    			{if (isupper(*tmp)) *tmp = tolower(*tmp);}
    		if (!strstr(rectext, gl_AppData.SearchText))
    			return;
    	}
    
    	rec_PrintShort(ptr);
    	
    	gl_AppData.LinesDisplayedSoFar++;
    	if (gl_AppData.LinesDisplayedSoFar == gl_AppCfg.LinesPerDisplay)
    	{
    		io_EnterToContinue();
    		gl_AppData.LinesDisplayedSoFar = 0;
    	}
    }
    
    /*
     ===============================================================================
        Function: 	rec_GetFieldPointer
        Args: 		A pointer to a Record struct, and a field type indicator
        Returns: 	A char pointer to the start of the field specified by the type indicator
        Purpose:	Quick way to obtain string from the Record struct.
     ===============================================================================
     */
    char *rec_GetFieldPointer(const struct Record *rec, enum RECORD_FIELDS WhichField)
    {
    	char *sptr;
    	switch (WhichField)
    	{
    		case RECF_NAME: sptr = (char *)rec->Name; break;
    		case RECF_PHONENUM1: sptr = (char *)rec->PhoneNum1; break;
    		case RECF_PHONENUM2: sptr = (char *)rec->PhoneNum2; break;
    		case RECF_PHONENUM3: sptr = (char *)rec->PhoneNum3; break;
    		case RECF_ADDRLINE1: sptr = (char *)rec->AddrLine1; break;
    		case RECF_ADDRLINE2: sptr = (char *)rec->AddrLine2; break;
    		case RECF_ADDRLINE3: sptr = (char *)rec->AddrLine3; break;
    		case RECF_ADDRLINE4: sptr = (char *)rec->AddrLine4; break;
    		case RECF_ADDRLINE5: sptr = (char *)rec->AddrLine5; break;
    		case RECF_EMAILADDR: sptr = (char *)rec->EmailAddr; break;
    		case RECF_MISC: sptr = (char *)rec->Misc; break;
    		default: sptr = (char *)rec->Name; break;
    	}
    	return (sptr);
    }
    UserInterfaceUtilities.c
    Code:
    #include "main.h"
    
    /*
     ===============================================================================
     	Author:		Hammer, May 2002, For www.cprogramming.com/cboard/
     	File:		UserInterfaceUtilities.c
     	Contents:	Menu structs
     				ui_DisplayMenu
    				ui_AddNewEntry
    				ui_UpdateEntry
    				ui_AddOrUpdateEntry
    				ui_DeleteEntry
    				ui_UndeleteEntry
    				ui_SearchAll
    				ui_SearchByID
    				ui_DisplayAll
    				ui_Save
    				ui_PurgeDeleted
    				ui_ShowInfo
    				ui_ToggleAutoSave
    				ui_SetLinesPerDisplay
     ===============================================================================
    */
    
    /*
     ===============================================================================
     	The following are the structs that make up the menus.
     ===============================================================================
    */
    
    static struct menu_item	OptionsMenu[] = 
    {
    	{"Auto-Save Setup", ui_ToggleAutoSave, NULL },
    	{"Set Lines Per Display", ui_SetLinesPerDisplay, NULL },
    	{ NULL, NULL, NULL }
    };
    
    static struct menu_item	SearchByFieldMenu[] =
    {
    	{ "Search By ID", ui_SearchAll, (void *)RECF_ID },
    	{ "Search By Name", ui_SearchAll, (void *)RECF_NAME },
    	{ "Search By Phone Num 1", ui_SearchAll, (void *)RECF_PHONENUM1 },
    	{ "Search By Phone Num 2", ui_SearchAll, (void *)RECF_PHONENUM2 },
    	{ "Search By Phone Num 3", ui_SearchAll, (void *)RECF_PHONENUM3 },
    	{ "Search By Phone Num 3", ui_SearchAll, (void *)RECF_PHONENUM3 },
    	{ "Search By Address Line 1", ui_SearchAll, (void *)RECF_ADDRLINE1 },
    	{ "Search By Address Line 2", ui_SearchAll, (void *)RECF_ADDRLINE2 },
    	{ "Search By Address Line 3", ui_SearchAll, (void *)RECF_ADDRLINE3 },
    	{ "Search By Address Line 4", ui_SearchAll, (void *)RECF_ADDRLINE4 },
    	{ "Search By Address Line 5", ui_SearchAll, (void *)RECF_ADDRLINE5 },
    	{ "Search By Email Address", ui_SearchAll, (void *)RECF_EMAILADDR },
    	{ "Search By Misc Field", ui_SearchAll, (void *)RECF_MISC },
    	{ NULL, NULL, NULL }
    };
    
    static struct menu_item	SortByFieldMenu[] =
    {
    	{ "Sort By ID", ui_SetSortBy, (void *)RECF_ID },
    	{ "Sort By Name", ui_SetSortBy, (void *)RECF_NAME },
    	{ "Sort By Phone Num 1", ui_SetSortBy, (void *)RECF_PHONENUM1 },
    	{ "Sort By Phone Num 2", ui_SetSortBy, (void *)RECF_PHONENUM2 },
    	{ "Sort By Phone Num 3", ui_SetSortBy, (void *)RECF_PHONENUM3 },
    	{ "Sort By Phone Num 3", ui_SetSortBy, (void *)RECF_PHONENUM3 },
    	{ "Sort By Address Line 1", ui_SetSortBy, (void *)RECF_ADDRLINE1 },
    	{ "Sort By Address Line 2", ui_SetSortBy, (void *)RECF_ADDRLINE2 },
    	{ "Sort By Address Line 3", ui_SetSortBy, (void *)RECF_ADDRLINE3 },
    	{ "Sort By Address Line 4", ui_SetSortBy, (void *)RECF_ADDRLINE4 },
    	{ "Sort By Address Line 5", ui_SetSortBy, (void *)RECF_ADDRLINE5 },
    	{ "Sort By Email Address", ui_SetSortBy, (void *)RECF_EMAILADDR },
    	{ "Sort By Misc Field", ui_SetSortBy, (void *)RECF_MISC },
    	{ NULL, NULL, NULL }
    };
    
    static struct menu_item	Page2Menu[] =
    {
    	{ "Search By Chosen Field", ui_DisplayMenu, SearchByFieldMenu },
    	{ "Display All Entries", ui_DisplayAll, NULL },
    	{ "Set Sort Order", ui_DisplayMenu, SortByFieldMenu },
    	{ "Undelete An Entry", ui_UndeleteEntry, NULL },
    	{ "Save Changes", ui_Save, NULL },
    	{ "Purge Entries Marked For Deletion", ui_PurgeDeleted, NULL },
    	{ "Application Options", ui_DisplayMenu, OptionsMenu },
    	{ NULL, NULL, NULL }
    };
    
    struct menu_item	MainMenu[] =
    {
    	{ "Add New Entry", ui_AddNewEntry, NULL },
    	{ "Delete Entry", ui_DeleteEntry, NULL },
    	{ "Update Entry", ui_UpdateEntry, NULL },
    	{ "Display Entry", ui_SearchAll, (void *)RECF_ID},
    	{ "Search By Name", ui_SearchAll, (void *)RECF_NAME },
    	{ "Save Changes", ui_Save, NULL },
    	{ "More Options", ui_DisplayMenu, Page2Menu },
    	{ "Help/About", ui_ShowInfo, NULL },
    	{ NULL, NULL, NULL }
    };
    
    /*
     ===============================================================================
     	Function: 	ui_DisplayMenu
        Args: 		Pointer to a menu to be displayed 
        Returns: 	Nothing
        Purpose:	Displays the menu, gets the users option and processes it.
     ===============================================================================
    */
    void ui_DisplayMenu(void *pmenu)
    {
    	int					i, choice;
    	struct menu_item *menu = pmenu;
    	struct menu_item	*item;
    
    	for (;;)
    	{
    		printf ("---> PhoneBook Menu <---\n");
    		for (i = 1, item = menu; item->Text != NULL; item++, i++)
    		{
    			printf("%2d %s\n", i, item->Text);
    		}
    
    		printf(" x Exit Menu\nEnter choice >");
    
    		if ((choice = io_GetInt(i-1)) == RC_BAD)
    			break;
    		item = menu + choice - 1;
    		if (item->Text == NULL) break;
    		(void) (*item->fptr) (item->args);
    	}
    }
    
    /*
     ===============================================================================
     	Function: 	ui_AddNewEntry
        Args: 		None (NULL pointer)
        Returns: 	Nothing
        Purpose:	Caller function to process a new entry request. 
        			The menu is capable of calling the next function directly,
        			but I left this in to assist future developments.
     ===============================================================================
    */
    void ui_AddNewEntry(void *Dummy)
    {
    	ui_AddOrUpdateEntry(Dummy);
    }
    
    /*
     ===============================================================================
     	Function: 	ui_UpdateEntry
        Args: 		None (NULL Pointer)
        Returns: 	Nothing
        Purpose:	Asks user for an ID, then calls another function to do record updates
     ===============================================================================
    */
    void ui_UpdateEntry(void *Dummy)
    {
    	struct Record *rec;
    	int choice;
    	
    	if (gl_HighestID == 0)
    	{
    		printf ("No entries in the database!\n");
    		io_EnterToContinue();
    		return;
    	}
    	
    	for (;;)
    	{
    		printf("Enter Record ID Number (1-%d, x to exit) >", gl_HighestID);
    		if ((choice = io_GetInt(gl_HighestID)) == RC_BAD)
    			return;
    		if ((rec = li_GetDataPtrByID(gl_AppData.MainList, choice)) == NULL)
    		{
    			printf("No such entry: %d\n", choice);
    		}
    		else break;
    	}
    	
    	ui_AddOrUpdateEntry(rec);
    }
    
    /*
     ===============================================================================
     	Function: 	ui_AddOrUpdateEntry
        Args: 		Pointer to a struct to be updated, OR NULL is request is to ADD.
        Returns: 	Nothing
        Purpose:	Obtains user data to complete a Record structure, then
        			adds or updates a record in the list.
     ===============================================================================
    */
    void ui_AddOrUpdateEntry(struct Record *newrec)
    {
    	struct Record rec;
    	char buffer[MAXL_FIELD+1];
    	struct Node *tmpListPtr;
    	
    	memset (&rec, 0, sizeof(struct Record));
    	/*
    	 * If newrec is not NULL, the it must be pointing to a Record struct and
    	 * therefore we are updating an existing record.  In this case, we show
    	 * the user the existing data before asking for new.  If they press <enter>
    	 * on a blank line, we leave the old data in place, this saves them retyping
    	 * each field when they are only changing one.
    	 */
    	if (newrec != NULL) 
    	{
    		rec = *newrec;
    		printf("(Press <Enter> on blank line to keep existing text)\nCurrent Name: %s\n", newrec->Name);
    	}
    	printf ("Enter Name (max %d chars) >", MAXL_NAME);
    	if (io_GetLine(buffer, MAXL_NAME+1, stdin) == 0)
    	{
    		if (newrec == NULL)
    			return; /* allow user to break out by entering a new record with no name */
    	}
    	else strncpy (rec.Name, buffer, MAXL_NAME+1);
    	
    	if (newrec != NULL) printf("Current 1st Phone: %s\n", newrec->PhoneNum1);
    	printf("Enter 1st Phone (max %d chars) >", MAXL_PHONENUM);
    	if (io_GetLine(buffer, MAXL_PHONENUM+1, stdin) > 0)
    		strncpy (rec.PhoneNum1, buffer, MAXL_PHONENUM+1);
    	
    	if (newrec != NULL) printf("Current 2nd Phone: %s\n", newrec->PhoneNum2);
    	printf("Enter 2nd Phone (max %d chars) >", MAXL_PHONENUM);
    	if (io_GetLine(buffer, MAXL_PHONENUM+1, stdin) > 0)
    		strncpy (rec.PhoneNum2, buffer, MAXL_PHONENUM+1);
    		
    	if (newrec != NULL) printf("Current 3rd Phone: %s\n", newrec->PhoneNum3);
    	printf("Enter 3rd Phone (max %d chars) >", MAXL_PHONENUM);
    	if (io_GetLine(buffer, MAXL_PHONENUM+1, stdin) > 0)
    		strncpy (rec.PhoneNum3, buffer, MAXL_PHONENUM+1);
    	
    	if (newrec != NULL) printf("Current Address Line 1: %s\n", newrec->AddrLine1);
    	printf("Enter Address Line 1 of 5 (max %d chars) >", MAXL_ADDR);
    	if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
    		strncpy (rec.AddrLine1, buffer, MAXL_ADDR+1);
    
    	if (newrec != NULL) printf("Current Address Line 2: %s\n", newrec->AddrLine2);
    	printf("Enter Address Line 2 of 5 (max %d chars) >", MAXL_ADDR);
    	if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
    		strncpy (rec.AddrLine2, buffer, MAXL_ADDR+1);
    	
    	if (newrec != NULL) printf("Current Address Line 3: %s\n", newrec->AddrLine3);
    	printf("Enter Address Line 3 of 5 (max %d chars) >", MAXL_ADDR);
    	if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
    		strncpy (rec.AddrLine3, buffer, MAXL_ADDR+1);
    	
    	if (newrec != NULL) printf("Current Address Line 4: %s\n", newrec->AddrLine4);
    	printf("Enter Address Line 4 of 5 (max %d chars) >", MAXL_ADDR);
    	if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
    		strncpy (rec.AddrLine4, buffer, MAXL_ADDR+1);
    	
    	if (newrec != NULL) printf("Current Address Line 5: %s\n", newrec->AddrLine5);
    	printf("Enter Address Line 5 of 5 (max %d chars) >", MAXL_ADDR);
    	if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
    		strncpy (rec.AddrLine5, buffer, MAXL_ADDR+1);
    			
    	if (newrec != NULL) printf("Current Email Address: %s\n", newrec->EmailAddr);
    	printf("Enter Email Address (max %d chars) >", MAXL_ADDR);
    	if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
    		strncpy (rec.EmailAddr, buffer, MAXL_ADDR+1);
    	
    	if (newrec != NULL) printf("Current Misc data: %s\n", newrec->Misc);
    	printf("Enter Misc Details (max %d chars) >", MAXL_MISC);
    	if (io_GetLine(buffer, MAXL_MISC+1, stdin) > 0)
    		strncpy (rec.Misc, buffer, MAXL_MISC+1);
    	
    	if (newrec == NULL) {rec.ID = gl_HighestID+1; rec.Status = 0;};
    	
    	/*
    	 * Display new/changed details for user to review
    	 */
    	printf ("Record Details Now:\n");
    	rec_PrintLong(&rec);
    	
    	if (io_GetYesNo("Confirm Details (y/n) >") == TRUE)
    	{	/* User has confirmed new details, so update/add record and add to list */
    		if (newrec)	/* updating an existing record */
    		{
    			*newrec = rec;
    		}
    		else
    		{	/* Adding a new record */
    			if ((newrec = malloc(sizeof (struct Record))) == NULL)
    			{
    				perror ("Add/Update failed on malloc");
    				return;
    			}
    			*newrec = rec;
    			if ((tmpListPtr = li_Insert (gl_AppData.MainList, newrec, rec_Compare)) == NULL)
    			{
    				printf("Error performing insert on list.  Item not addded.\n");
    				free(newrec);	/* throw the record away */
    			}
    			else 
    			{	
    				gl_AppData.MainList = tmpListPtr;
    				newrec->ID = gl_HighestID;
    			}
    		}
    		gl_AppData.DataChanged = TRUE;
    		if (gl_AppCfg.AutoSave) (void)io_WriteAllRecordsToFile(gl_AppData.MainList);
    	}
    	
    }
    
    /*
     ===============================================================================
     	Function: 	ui_DeleteEntry
        Args: 		None (NULL pointer)
        Returns: 	Nothing
        Purpose:	Gets an ID from the user, displays the record then marks it
        			for deletion if the user agrees.  If autosave is on, the
        			list will be written to disk and the record will be erased
        			completely (undelete not available in this case).
     ===============================================================================
    */
    void ui_DeleteEntry(void *Dummy)
    {
    	struct Record *rec;
    	int choice;
    
    	if (gl_HighestID == 0)
    	{
    		printf ("No entries in the database!\n");
    		io_EnterToContinue();
    		return;
    	}
    	
    	printf("Enter Record ID Number (1-%d, x to exit) >", gl_HighestID);
    	if ((choice = io_GetInt(gl_HighestID)) == RC_BAD)
    		return;
    	if ((rec = li_GetDataPtrByID(gl_AppData.MainList, choice)) == NULL)
    	{
    		printf("No such entry: %d\n", choice);
    		io_EnterToContinue();
    		return;
    	}
    	
    	rec_PrintLong(rec);
    	
    	if (io_GetYesNo("Confirm Delete (y/n) >") == TRUE)
    	{
    		rec->Status = rec->Status | ST_DELETED;
    		gl_AppData.DataChanged = TRUE;
    		if (gl_AppCfg.AutoSave) (void)io_WriteAllRecordsToFile(gl_AppData.MainList);
    	}
    }
    
    /*
     ===============================================================================
     	Function: 	ui_UndeleteEntry
        Args: 		None (NULL Pointer)
        Returns: 	Nothing
        Purpose:	Gets an ID from the user, and unsets the deleted flag.
     ===============================================================================
    */
    void ui_UndeleteEntry(void *Dummy)
    {
    	struct Record *rec;
    	int choice;
    	
    	if (gl_HighestID == 0)
    	{
    		printf ("No entries in the database!\n");
    		io_EnterToContinue();
    		return;
    	}
    	
    	printf("Enter Record ID Number (1-%d, x to exit) >", gl_HighestID);
    	if ((choice = io_GetInt(gl_HighestID)) == RC_BAD)
    		return;
    	if ((rec = li_GetDataPtrByID(gl_AppData.MainList, choice)) == NULL)
    	{
    		printf("No such entry: %d\n", choice);
    		io_EnterToContinue();
    		return;
    	}
    	
    	if (rec->Status & ST_DELETED)
    	{
    		rec->Status = rec->Status ^ ST_DELETED;
    		printf ("Record undeleted\n");
    		gl_AppData.DataChanged = TRUE;
    		if (gl_AppCfg.AutoSave) (void)io_WriteAllRecordsToFile(gl_AppData.MainList);
    	}
    	else
    		printf ("Record was not marked for deletion\n");
    	
    	io_EnterToContinue();
    	
    }
    
    /*
     ===============================================================================
     	Function: 	ui_SearchByID
        Args: 		None
        Returns: 	Nothing
        Purpose:	Gets an ID from the user, then display that Record in detail
     ===============================================================================
    */
    void ui_SearchByID(void)
    {
    	struct Record *rec;
    	int choice;
    	
    	if (gl_HighestID == 0)
    	{
    		printf ("No entries in the database!\n");
    		io_EnterToContinue();
    		return;
    	}
    	printf("Enter Record ID Number (1-%d, x to exit) >", gl_HighestID);
    	if ((choice = io_GetInt(gl_HighestID)) == RC_BAD)
    		return;
    	if ((rec = li_GetDataPtrByID(gl_AppData.MainList, choice)) == NULL)
    		printf("No such entry: %d\n", choice);
    	else rec_PrintLong(rec);
    	io_EnterToContinue();
    }
    
    /*
     ===============================================================================
     	Function: 	ui_SearchAll
        Args: 		Field type
        Returns: 	Nothing
        Purpose:	Gets search criteria from user, the traverses the list, short
        			printing each record that matches the criteria.
     ===============================================================================
    */
    void ui_SearchAll(void *WhichField)
    {
    	char	buffer[MAXL_FIELD + 1];
    	int		i, len;
    
    	if (gl_HighestID == 0)
    	{
    		printf ("No entries in the database!\n");
    		io_EnterToContinue();
    		return;
    	}
    	
    	if ((enum RECORD_FIELDS)WhichField == RECF_ID) 
    	{
    		ui_SearchByID();
    		return;
    	}
    	
    	printf("Enter Criteria >");
    	
    	if ((len = io_GetLine(buffer, MAXL_FIELD + 1, stdin)) != 0)
    	{
    		gl_AppData.SearchField = (enum RECORD_FIELDS)WhichField;
    		for (i = 0; i < len; i++)	/* convert to lower case for search */
    			if (isupper(buffer[i])) buffer[i] = tolower(buffer[i]);
    		strncpy(gl_AppData.SearchText, buffer, MAXL_FIELD + 1);
    		gl_AppData.LinesDisplayedSoFar = 0;
    		(void)li_Traverse(gl_AppData.MainList, rec_SearchAndPrint);
    	}
    
    	io_EnterToContinue();
    }
    
    /*
     ===============================================================================
     	Function: 	ui_DisplayAll
        Args: 		None
        Returns: 	Nothing
        Purpose:	Traverses the list and does a short print of each record
     ===============================================================================
    */
    void ui_DisplayAll(void *Dummy)
    {
    	if (gl_HighestID == 0)
    	{
    		printf ("No entries in the database!\n");
    		io_EnterToContinue();
    		return;
    	}
    	
    	gl_AppData.LinesDisplayedSoFar = 0;
    	gl_AppData.SearchField = RECF_DUMMY;
    	(void)li_Traverse(gl_AppData.MainList, rec_SearchAndPrint);
    	io_EnterToContinue();
    }
    
    /*
     ===============================================================================
     	Function: 	ui_Save
        Args: 		None (NULL Pointer)
        Returns: 	Nothing
        Purpose:	Gets confirmation from the user, then saves the current list to disk
     ===============================================================================
    */
    void ui_Save(void *Dummy)
    {
    	if (io_GetYesNo("Confirm Save (including purge of deleted records) (y/n) >") == TRUE)
    	{
    		(void)io_WriteAllRecordsToFile(gl_AppData.MainList);
    	}
    }
    
    /*
     ===============================================================================
     	Function: 	ui_PurgeDeleted
        Args: 		None (NULL Pointer)
        Returns: 	Nothing
        Purpose:	Gets confirmation from the user, then traverses the list,
        			removing (free'ing) any records that are marked as deleted
     ===============================================================================
    */
    void ui_PurgeDeleted(void *Dummy)
    {
    	unsigned char status = ST_DELETED;
    	
    	if (gl_HighestID == 0)
    	{
    		printf ("No entries in the database!\n");
    		io_EnterToContinue();
    		return;
    	}
    	
    	if (io_GetYesNo("Confirm Purge (y/n) >") == TRUE)
    		gl_AppData.MainList = li_DeleteNodeAndData (gl_AppData.MainList, &status, rec_CompareStatus);
    }
    
    /*
     ===============================================================================
        Function: 	ui_ShowInfo
        Args: 		None (NULL Pointer)
        Returns: 	Nothing
        Purpose:	Displays stats about the program and the PhoneBook
     ===============================================================================
     */
    void ui_ShowInfo(void *Dummy)
    {
    	printf ("--->\n---> **PhoneBook** - Version: %s\n", PRG_VERSION);
    	printf ("---> Written By *Hammer* for the C Programming Contest\n");
    	printf ("---> held by http://www.cprogramming.com/cboard/ in May 2002\n");
    	printf ("---> You can contact the author via email: [email protected]\n");
    	printf ("--->\n---> The PhoneBook datafile is %s\n", STR_FILENAME);
    	printf ("---> The PhoneBook configuration file is %s\n", STR_CFG_FILENAME);
    	printf ("--->\n---> There are currently %d entries loaded.\n", li_Count(gl_AppData.MainList));
    	printf ("--->\n---> This program accepts command line information\n");
    	printf ("---> For example (assuming the program name is phone.exe):\n");
    	printf ("--->   >phone.exe hammer tommy\n");
    	printf ("---> will result in the database being searched for the\n");
    	printf ("---> names hammer and tommy and the results being displayed.\n");
    	printf ("---> In this case, the menu is not loaded, and the\n");
    	printf ("---> program terminates.  This is useful for quick lookups.\n");
    	printf ("--->\n");
    	io_EnterToContinue();
    }
    
    /*
     ===============================================================================
     	Function: 	ui_ToggleAutoSave
        Args: 		None (NULL Pointer)
        Returns: 	Nothing
        Purpose:	Allows the user to turn auto-save off and on.
     ===============================================================================
    */
    void ui_ToggleAutoSave(void *Dummy)
    {
    	printf ("Auto-Save is currently %s\n", (gl_AppCfg.AutoSave)?"On":"Off");
    	if (io_GetYesNo("Toggle auto-save (y/n) >") == TRUE)
    	{
    		gl_AppCfg.AutoSave = ~gl_AppCfg.AutoSave;
    		printf ("Auto-Save is now %s\n", (gl_AppCfg.AutoSave)?"On":"Off");
    		io_EnterToContinue();
    	}
    }
    
    /*
     ===============================================================================
     	Function: 	ui_SetLinesPerDisplay
        Args: 		None (NULL Pointer)
        Returns: 	Nothing
        Purpose:	Allows the user to set the number of lines displayed in lists
        			before a io_EnterToContinue is called.
     ===============================================================================
    */
    void ui_SetLinesPerDisplay(void *Dummy)
    {
    	int Lines;
    	
    	printf ("This option determines how many lines are output to the screen\nbefore a \"hit enter to continue\" message is displayed\n");
    	printf ("The current setting is %d\n", gl_AppCfg.LinesPerDisplay);
    	printf ("Enter a new number or x to exit >");
    	
    	if ((Lines = io_GetInt(MAX_LINES_PER_PAGE)) == RC_BAD)
    		return;
    		
    	gl_AppCfg.LinesPerDisplay = Lines;
    	printf ("The new setting is %d\n", gl_AppCfg.LinesPerDisplay);
    	io_EnterToContinue();
    }
    
    /*
     ===============================================================================
     	Function: 	ui_SetSortBy
        Args: 		Field type
        Returns: 	Nothing
        Purpose:	Allows the user to set the sort order
     ===============================================================================
    */
    void ui_SetSortBy(void *WhichField)
    {
    	gl_AppCfg.SortField = (enum RECORD_FIELDS)WhichField;
    	
    	printf ("Sorting the data...");
    	li_Sort (gl_AppData.MainList, rec_Compare); 
    	printf ("Done!\n");
    	io_EnterToContinue();
    }
    main.h
    Code:
    /*
     ===============================================================================
     	Author:		Hammer, May 2002, For www.cprogramming.com/cboard/
     	File:		main.h
     	Contents:	All the definitions needed for the app, along with the
     				function prototypes.  This is the only header file used
     				by this program.
     ===============================================================================
    */
    #ifndef __PHONE__
    #define __PHONE__
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <ctype.h>
    
    /*
     * #defines
     * Field lengths - These do NOT include the 1 byte for the NULL terminator
     */
    #define MAXL_NAME		100
    #define MAXL_PHONENUM	20
    #define MAXL_MISC		256
    #define MAXL_FIELD		256
    #define MAXL_ADDR		100
    #define MAXL_FIELD_NAME 50
    
    
    #define DEF_LINES_PER_PAGE 10
    #define MAX_LINES_PER_PAGE 999
    
    /* 
     * String variables 
     */
    #define STR_FILENAME	"phonebk.dat"
    #define STR_CFG_FILENAME	"phonebk.cfg"
    #define PRG_VERSION 	"1.1"
    #define RC_BAD -1
    
    /*
     * boolean style vars 
     */
    #define TRUE	1
    #define FALSE	0
    
    /*
     * Record status flags 
     */
    #define ST_DELETED	0x1
    
    /*
     * Macros 
     */
    #define FLUSH_INPUT while(getchar() != '\n')
    
    /*
     * enums
     */
    enum RECORD_FIELDS {RECF_DUMMY, RECF_ID, RECF_NAME, 
    					RECF_PHONENUM1, RECF_PHONENUM2, RECF_PHONENUM3, 
    					RECF_ADDRLINE1, RECF_ADDRLINE2, RECF_ADDRLINE3,
    					RECF_ADDRLINE4, RECF_ADDRLINE5, RECF_EMAILADDR,
    					RECF_MISC, RECF_END_MARKER};
    
    /*
     * typedefs
     */
    typedef int	bool_t;
    
    /*
     * Structures
     */
    struct Record
    {
    	int				ID;						/* Assigned at load time, sequential based on input, not related to tree */
    	char			Name[MAXL_NAME + 1];	/* All in name field, holds any type, so user can decide own format */
    	char			PhoneNum1[MAXL_PHONENUM + 1];	/* 3 phone num fields, user decides what to put where */
    	char			PhoneNum2[MAXL_PHONENUM + 1];
    	char			PhoneNum3[MAXL_PHONENUM + 1];
    	char			AddrLine1[MAXL_ADDR+1];
    	char			AddrLine2[MAXL_ADDR+1];
    	char			AddrLine3[MAXL_ADDR+1];
    	char			AddrLine4[MAXL_ADDR+1];
    	char			AddrLine5[MAXL_ADDR+1];
    	char			EmailAddr[MAXL_ADDR+1];
    	char			Misc[MAXL_MISC + 1];			/* Freeform text field, for user comments. */
    	unsigned char	Status; /* See the ST_... variables */
    };
    
    struct Node
    {
    	void		*DataPtr;
    	struct Node *Next;
    };
    
    struct appdata
    {
    	struct Node 		*MainList;
    	enum RECORD_FIELDS	SearchField;
    	char				SearchText[MAXL_FIELD + 1];
    	bool_t				DataChanged;
    	int 				LinesDisplayedSoFar;
    };
    
    struct appconfig
    {
    	bool_t AutoSave;
    	int LinesPerDisplay;
    	enum RECORD_FIELDS	SortField;	
    };
    
    struct menu_item
    {
    	char	*Text;
    	void 	(*fptr) (void *);
    	void 	*args;
    };
    
    /*
     * more typedefs
     */
    typedef void (*FPTR_Action) (void *);
    typedef int (*FPTR_Compare) (const struct Record *, const struct Record *);
    typedef int (*FPTR_CompareDelete) (const struct Record *, void *);
    
    /*
     * Global vars / externs
     */
    extern int	gl_HighestID;
    extern struct appdata gl_AppData;
    extern struct appconfig gl_AppCfg;
    extern struct menu_item MainMenu[];
    
    /*
     * Prototypes for ListUtilities
     */
    struct Node *li_Create(void);
    struct Node *nd_Create(void *, struct Node *);
    void		nd_Destroy(struct Node *);
    int			li_Traverse(struct Node *, FPTR_Action);
    struct Node *li_Insert(struct Node *, void *, FPTR_Compare);
    struct Node *li_Destroy(struct Node *);
    struct Record *li_GetDataPtrByID(struct Node *, int );
    struct Node *li_DeleteNodeAndData(struct Node *, void *, FPTR_CompareDelete);
    int 		li_Count(struct Node *);
    void li_Sort(struct Node *, FPTR_Compare);
    
    /*
     * Prototypes for RecordUtilities
     */
    int			rec_Compare(const struct Record *, const struct Record *);
    int			rec_CompareID(const struct Record *, void *);
    int 		rec_CompareStatus(const struct Record *rec, void *);
    void		rec_PrintShort(void *);
    void 		rec_PrintLong(void *);
    void		rec_SearchAndPrint(void *);
    char 		*rec_GetFieldPointer(const struct Record *, enum RECORD_FIELDS);
    
    /*
     * Prototypes for ioUtilities
     */
    void		io_WriteRecordToFile(void *);
    struct Node *io_LoadPhoneBookFromFile(struct Node *);
    int			io_WriteAllRecordsToFile(struct Node *);
    int 		io_GetInt(int);
    void 		io_EnterToContinue(void);
    int 		io_GetLine(char *, int , FILE *);
    bool_t		io_GetYesNo(char *);
    void 		io_LoadAppConfig(void);
    void 		io_SaveAppConfig(void);
    
    /*
     * Prototypes for UserInterfaceUtilities
     */
    void 		ui_AddNewEntry(void *);
    void 		ui_DeleteEntry(void *);
    void 		ui_UndeleteEntry(void *);
    void 		ui_UpdateEntry(void *);
    void 		ui_AddOrUpdateEntry(struct Record *);
    void 		ui_DisplayAll(void *);
    void 		ui_Save(void *);
    void 		ui_PurgeDeleted(void *);
    void 		ui_SearchAll(void *);
    void 		ui_SearchByID(void);
    void 		ui_DisplayMenu(void *);
    void 		ui_ShowInfo(void *);
    void 		ui_ToggleAutoSave(void *);
    void 		ui_SetLinesPerDisplay(void *);
    void 		ui_SetSortBy(void *);
    #endif

  7. #7
    Just because ygfperson's Avatar
    Join Date
    Jan 2002
    Posts
    2,490
    and... Hammer wins! Hail to the Hammer!
    Cicero... there's always the next one.
    good job to the contestants, and thanks to the judges and their hard work.

  8. #8
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Congratulations to Hammer, and Cicero, too...they both deserve credit for their effort. Hammer's entry was very complex, and a little hard to follow sometimes. But all in all he deserves the most credit for his hard work. Cicero, on the other hand was better at stubbing out the implementation first, a wise move. But his implementation seemed a little weak. So I would have chosen Hammer for that reason. Cicero, Hammer, what have you two got to say about the results?
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  9. #9
    Congrats to Hammer.

  10. #10
    End Of Line Hammer's Avatar
    Join Date
    Apr 2002
    Posts
    6,231
    Hey, what d'ya know... I won!!

    Naturally I'm pleased, and would like to pass my thanks to all those involved, and my condolences to Cicero.

    I don't have a speech prepared, you'll be glad to hear, nor do I have a mile long list of people to thank!

    I have attached a copy of my source, zipped, with a Windows exe. If anyone wants to actually make use of this program, they are more than welcome to (just leave my name in there!), I Also welcome any more comments on it.

    As for the comments by the judges, they're all fair and valid, and it's nice to see that all entries were given a fair chance, and were thoroughly examined. I like to know what other people think of my ideas, and this is definately a good way to do it.

    The only thing I would like to discuss further is my choice of list. I didn't use a tree because the search facility was designed to check each and every node (it used strstr() to find strings within strings in any given field). If the list was a tree, it would have to traverse all the way to the left doing nothing, then all the way back to the far right, doing compares on each node as it went. Using the link list method, you just start at the beginning and traverse to the end, therefore isn't this faster? I admit that adding a new node would be slower, but as most of the time the user will be searching, rather than adding, I thought link list was the way to go. What do you think?

    One bad point about the contest: only 2 entries. I'd have liked to seen more versions of the code to see how else it could have been done. Maybe next time...... Speaking of which, what is it and when?

    When all else fails, read the instructions.
    If you're posting code, use code tags: [code] /* insert code here */ [/code]

  11. #11
    Code Goddess Prelude's Avatar
    Join Date
    Sep 2001
    Posts
    9,897
    The only thing I would like to discuss further is my choice of list. I didn't use a tree because the search facility was designed to check each and every node (it used strstr() to find strings within strings in any given field). If the list was a tree, it would have to traverse all the way to the left doing nothing, then all the way back to the far right, doing compares on each node as it went. Using the link list method, you just start at the beginning and traverse to the end, therefore isn't this faster? I admit that adding a new node would be slower, but as most of the time the user will be searching, rather than adding, I thought link list was the way to go. What do you think?
    A good and valid reason. A binary tree searches no faster in the worst case situation than a linked list but insertion into the tree is quadratic. Though you still sacrifice efficiency since most users will search primarily for the name of the record instead of less memorable fields. Since you perform quite a bit of sorting, reading sorted data into a binary tree is expensive, and balancing routines are such a pain, I'd have to say that a linked list is a good implementation choice but could be better if specialized for searching. You lose the simplicity you have now, but consider how a skip list would improve your searching for a name in a sorted list.

    -Prelude
    My best code is written with the delete key.

  12. #12
    End Of Line Hammer's Avatar
    Join Date
    Apr 2002
    Posts
    6,231
    Thanks for your reply Prelude.....

    >but consider how a skip list ....
    What, there's another type of list?!! Guess I'll have to go read up about that one now.
    When all else fails, read the instructions.
    If you're posting code, use code tags: [code] /* insert code here */ [/code]

  13. #13
    Code Goddess Prelude's Avatar
    Join Date
    Sep 2001
    Posts
    9,897
    >What, there's another type of list?!! Guess I'll have to go read up about that one now.
    You'll have fun with that one, imagine a linked list with extra links that skip over a certain number of nodes to speed up searching. So you have the lowest level which is a regular list, then another level with a link every 3rd or so node. It's a lot like a binary tree, and is often considered as an alternative.
    http://www.google.com/search?hl=en&i...s%3A+Skip+list

    -Prelude
    My best code is written with the delete key.

  14. #14
    Banned Troll_King's Avatar
    Join Date
    Oct 2001
    Posts
    1,784
    Good job Son, I like your program.

  15. #15
    End Of Line Hammer's Avatar
    Join Date
    Apr 2002
    Posts
    6,231
    Originally posted by Troll_King
    Good job Son, I like your program.
    Thanks Dad
    When all else fails, read the instructions.
    If you're posting code, use code tags: [code] /* insert code here */ [/code]

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Obfuscated Code Contest: The Results
    By Stack Overflow in forum Contests Board
    Replies: 29
    Last Post: 02-18-2005, 05:39 PM
  2. Text Compression Contest -- Sept. 28, 2002
    By ygfperson in forum A Brief History of Cprogramming.com
    Replies: 4
    Last Post: 10-03-2002, 03:20 PM
  3. Text Compression Contest -- Sept. 28, 2002
    By ygfperson in forum C++ Programming
    Replies: 5
    Last Post: 10-03-2002, 03:20 PM
  4. Text Compression Contest -- Sept. 28, 2002
    By ygfperson in forum C Programming
    Replies: 3
    Last Post: 10-01-2002, 06:25 PM
  5. Results for the Encryption Contest -- June 23, 2002
    By ygfperson in forum A Brief History of Cprogramming.com
    Replies: 18
    Last Post: 07-07-2002, 08:04 AM