[comp.sources.games] v12i047: tmc - tinymud client for non-networked System V, Part01/01

billr@saab.tek.com (Bill Randle) (03/09/91)

Submitted-by: John Temples <jwt!john@uunet.UU.NET>
Posting-number: Volume 12, Issue 47
Archive-name: tmc/Part01
Environment:  System V

	[This goes along with the System V patches previously
	 posted to provide tinymud operation on non-networked
	 System V systems. (Untested by me.)  -br]

#!/bin/sh
# This is a shell archive (shar 3.32)
# made 03/08/1991 15:39 UTC by john@john
#
# existing files WILL be overwritten
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#   2443 -rw-r--r-- tmc.doc
#    139 -rw-r--r-- Makefile
#   1925 -rw-r--r-- tmc.h
#  11599 -rw-rw-rw- tmc.c
#
if touch 2>&1 | fgrep 'amc' > /dev/null
 then TOUCH=touch
 else TOUCH=true
fi
# ============= tmc.doc ==============
echo "x - extracting tmc.doc (Text)"
sed 's/^X//' << 'SHAR_EOF' > tmc.doc &&
X                Client for Non-Networked System V TinyMUD
X
Xtmc is a curses-based client for the non-network System V version of
XTinyMUD.  It replaces tinymud.sh which is provided with the
Xdistributed package.  
X
X
XInstallation:
X
XLook at tmc.h.  The only thing that you're likely to need to change
Xis the FIFO #define which points to the named pipes used by the
Xserver.  This is the directory in which you started the server.  The
Xexecutable can be put in any public place.  'make' will build the
Xexecutable.
X
XStarting tmc:
X
XType 'tmc' to start the program.  The TinyMUD server (fifonetmud)
Xshould already be running.  tmc provides a split-screen display, with
Xthe user's commands being echoed in the lower window, and responses
Xfrom the server appearing in the upper window.  Word wrapping is
Xprovided in both windows.
X
XOn startup, tmc reads a startup file (default: ~/.tmcrc) and
Xtransmits its contents to the server.  This is useful for automatic
Xlogin.
X
X
XCommand line options:
X
X-a filename
X	Use 'filename' as an alternate startup file rather than the
X	compiled-in default.  Tilde expansion is supported.
X
X-c frequency
X	Use 'frequency' as the clock update frequency (in seconds) rather
X	than the compiled-in default.  A frequency of zero disables the
X	clock display.
X
X-e
X	Echo commands typed by the player into the remote window.
X
X-h
X	Disable hardware screen scrolling.  Hardware screen scrolling can
X	be visually annoying, but runs much faster.
X
X-l lines
X	Use 'lines' as the size of the local (lower) window rather than
X	the compiled-in default.
X
X-r
X	Ignore the startup file, if any.
X
X
XDefault command line options may be assigned to the environment
Xvariable TMCINIT.  Any options explicitly stated on the command line
Xwill override any found in TMCINIT.
X
X
XKeyboard commands:
X
XText entered by the user is echoed in the local window, and
Xtransmitted to the server when the enter key is pressed.  Word
Xwrapping is provided on text as it is typed.  Certain keys have a
Xspecial meaning:
X
XBackspace or DEL:
X	The previous character is deleted.  Backspace wrapping to the
X	previous line is not handled correctly.
X
XControl U:
X	The current line is discarded.
X
XControl L:
X	The screen is redrawn.
X
XInterrupt:
X	Disconnect from the server and exit the program.  Also, typing 
X	QUIT (all capitals) will tell the server to disconnect you.  When 
X	tmc detects a server disconnect, it exits.
X
X--
XDirect questions or comments to John Temples (john@jwt.UUCP).
SHAR_EOF
$TOUCH -am 0308102491 tmc.doc &&
chmod 0644 tmc.doc ||
echo "restore of tmc.doc failed"
set `wc -c tmc.doc`;Wc_c=$1
if test "$Wc_c" != "2443"; then
	echo original size 2443, current size $Wc_c
fi
# ============= Makefile ==============
echo "x - extracting Makefile (Text)"
sed 's/^X//' << 'SHAR_EOF' > Makefile &&
XCC = gcc
XCFLAGS = -O
XLDFLAGS = -s
XLIBS = -lcurses -lc_s
X
Xtmc:	tmc.o
X	$(CC) tmc.o $(LDFLAGS) -o tmc $(LIBS)
X	mcs -d tmc
X
Xtmc.o:	tmc.c tmc.h
SHAR_EOF
$TOUCH -am 0304123191 Makefile &&
chmod 0644 Makefile ||
echo "restore of Makefile failed"
set `wc -c Makefile`;Wc_c=$1
if test "$Wc_c" != "139"; then
	echo original size 139, current size $Wc_c
fi
# ============= tmc.h ==============
echo "x - extracting tmc.h (Text)"
sed 's/^X//' << 'SHAR_EOF' > tmc.h &&
X/* Configuration items for tmc
X */
X
X/* tab size: 4 */
X
X#define FIFO "/usr2/local/lib/mud/"		/* directory containing server FIFOs */
X
X#define DEF_LOCAL_SIZE	5				/* default size of local window */
X#define MAXTRIES		10				/* how many connect attempts */
X#define TINYPORT		4201			/* Pseudo-port number of server */
X#define DEF_CLOCK_FREQ		5			/* clock update freq. (secs) */
X#define DEF_RCFILE		"~/.tmcrc"		/* startup file */
X
X/* Global variables
X */
X
Xextern	char	*optarg;
X
Xtypedef	struct	{
X	long	mtype;
X	char	msg[BUFSIZ+1];
X} msg_t;
X
Xint		inport;					/* file descriptor for reading from server */	
Xint		outport;				/* file descriptor for writing to server */
Xint		msqid;					/* id of message queue */
Xint		readpid;				/* pid of child process reading server */
Xint		writepid;				/* pid of child process reading keyboard */
XWINDOW	*local;					/* local (bottom) window */
XWINDOW	*remote;				/* remote (top) window */
XWINDOW	*status;				/* center dividing window */
X
Xint		hw_scroll = TRUE;				/* use hardware screen scrolling? */
Xint		echo_cmds = FALSE;				/* echo commands to remote window */
Xint		wants_rc = TRUE;				/* should we read the startup file? */
X
Xint		local_size = DEF_LOCAL_SIZE;	/* default size of local window */
Xint		server_connected = FALSE;		/* have we connected with the server? */
Xint		clock_ticked = FALSE;			/* has the timer alarm gone off? */
Xtime_t	start_time;						/* time our session started */
Xint		clock_freq = DEF_CLOCK_FREQ;	/* clock tick frequency */
X
X/* Function declarations
X */
X
Xchar	*getenv();
X
Xvoid	make_queue();
Xvoid	init_windows();
Xvoid	connect_server();
Xvoid	error();
Xvoid	exit_prog();
Xvoid	spawn_children();
Xvoid	add_queue(char *, int, int);
Xvoid	read_queue();
Xvoid	message(char *);
Xvoid	process_input(char);
Xvoid	process_output(char *, int, int);
Xchar	*reformat(char *);
Xvoid	clock_tick();
Xvoid	update_clock();
Xint		special(char *);
Xchar	*strstr(char *, char *);
X
X#define CTL(c) (c & 0x1F)
SHAR_EOF
$TOUCH -am 0308100391 tmc.h &&
chmod 0644 tmc.h ||
echo "restore of tmc.h failed"
set `wc -c tmc.h`;Wc_c=$1
if test "$Wc_c" != "1925"; then
	echo original size 1925, current size $Wc_c
fi
# ============= tmc.c ==============
echo "x - extracting tmc.c (Text)"
sed 's/^X//' << 'SHAR_EOF' > tmc.c &&
X/* Copyright (c) 1991, John W. Temples, III.  All rights reserved. */
X/* This program may be freely redistributed and modified. */
X
X/* Client for non-network System V version of TinyMUD.
X*/
X
X/* tab size: 4 */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <curses.h>
X#include <sys/fcntl.h>
X#include <errno.h>
X#include <string.h>
X#include <sys/ipc.h>
X#include <signal.h>
X#include <time.h>
X
X#include "tmc.h"
X
Xvoid	main(int argc, char *argv[])
X{
Xint		c, i, nargc;
Xchar	buf[BUFSIZ];
XFILE	*rcfile;
Xchar	rcpath[128];
Xchar	*cptr;
Xchar	*nargv[32];
X
Xstrcpy(rcpath, DEF_RCFILE);
X
X/* process command line arguments */
X
X/* first pick up default switches */
X
Xnargc = 1;								/* leave 0 empty for program name */
X
Xif (cptr = getenv("TMCINIT"))
X	if (nargv[nargc] = strtok(cptr, " "))
X		while (nargv[++nargc] = strtok(NULL, " "))
X			;
X
X/* tack on command line switches */
X
Xnargv[0] = argv[0];		/* program name */
X
Xfor (i = 1; i < argc; i++)
X	nargv[nargc++] = argv[i];
X
Xwhile ((c = getopt(nargc, nargv, "a:c:ehl:r")) != -1) {
X	switch (c) {
X		case 'a':						/* alternate rc file */
X			strcpy(rcpath, optarg);
X			break;
X			
X		case 'c':						/* clock tick frequency */
X			clock_freq = atoi(optarg);
X			break;
X			
X		case 'e':						/* command echo */
X			echo_cmds = !echo_cmds;
X			break;
X			
X		case 'h':						/* no hardware screen scrolling */
X			hw_scroll = !hw_scroll;
X			break;
X			
X		case 'l':						/* local window size */
X			local_size = atoi(optarg);
X			break;
X		
X		case 'r':						/* ignore startup file */
X			wants_rc = !wants_rc;
X			break;
X
X		default:
X			fprintf(stderr, "usage: %s [-a rcfile] [-c freq] [-l lines] [-ehr]\n", argv[0]);
X			exit(1);
X	}
X}
X
X/* set up the screen windows */
X
Xinit_windows();
X
X/* handle signals */
X
Xsignal(SIGINT, exit_prog);
Xsignal(SIGQUIT, exit_prog);
Xsignal(SIGALRM, clock_tick);
Xsignal(SIGSEGV, exit_prog);
Xsignal(SIGILL, exit_prog);
Xsignal(SIGBUS, exit_prog);
X
X/* connect to the TinyMUD server */
X
Xconnect_server();
X
X/* create the message queue the children will use to talk to us */
X
Xmake_queue();
X
X/* create the child processes */
X
Xspawn_children();
X
X/* read the startup file */
X
Xif (wants_rc) {
X	if (rcpath[0] == '~') {
X		if ((cptr = getenv("HOME")) != NULL) {
X			strcpy(buf, &rcpath[1]);
X			strcpy(rcpath, cptr);
X			strcat(rcpath, buf);
X		}
X	}
X	if ((rcfile = fopen(rcpath, "r")) != NULL) {
X		while (fgets(buf, sizeof(buf), rcfile))
X			write(outport, buf, strlen(buf));
X		fclose(rcfile);
X	}
X}
X
Xalarm(clock_freq);
X
Xstart_time = time(NULL);
X
X/* read the message queue and process.  never returns */
X
Xread_queue();
X
X}
X
X
X
Xvoid	make_queue()
X{
Xchar	*buf[128];
Xkey_t	key;
X
Xsprintf(buf, "%sfifor%d", FIFO, getpid());
X
Xif ((key = ftok(buf, 'M')) == (key_t)-1)
X	error("ftok() failed");
X
Xif ((msqid = msgget(key, 0)) >= 0 && msgctl(msqid, IPC_RMID, 0) == -1)
X	error("Can't remove old message queue id");
X
Xif ((msqid = msgget(key, 0600 | IPC_CREAT)) == -1)
X	error("Can't create new message queue");
X
X}
X
X
X
Xvoid	init_windows()
X{
Xint		i;
X
X	
Xinitscr();
Xcbreak();
Xnoecho();
X
Xstatus = newwin(1, COLS, LINES - local_size - 1, 0);
Xlocal = newwin(local_size, COLS, LINES - local_size, 0);
Xremote = newwin(LINES - local_size - 1, COLS, 0, 0);
X
Xscrollok(local, TRUE);
Xscrollok(remote, TRUE);
X
Xif (hw_scroll) {
X	idlok(local, TRUE);
X	idlok(remote, TRUE);
X}
X
Xfor (i = 0; i < COLS/2-5; i++)
X	waddch(status, ACS_HLINE);
X
Xwaddstr(status, "[TinyMUD!]");
X
Xfor (i = 0; i < COLS/2-5; i++)
X	waddch(status, ACS_HLINE);
X
Xwrefresh(status);
X}
X
X
X
Xvoid	connect_server()
X{
Xchar	buf[128];
Xint		mypid = getpid();
Xint		tries = 0;
Xint		server;
X
X
Xmessage("[Connecting to the TinyMUD server --  please wait]");
X
Xsprintf(buf, "%sfifor%d", FIFO, TINYPORT);
X
Xif ((server = open(buf, O_WRONLY)) == -1)
X	error("Couldn't find server");
X
Xsprintf(buf, "%d\n", mypid);
X
Xwrite(server, buf, strlen(buf));
X
Xclose(server);
X
Xsprintf(buf, "%sfifor%d", FIFO, mypid);
X
Xdo {
X	sleep(1);
X	if ((outport = open(buf, O_WRONLY)) == -1)
X		if (errno != ENOENT)
X			error("Couldn't open port to server");
X		else
X			continue;
X	else
X		break;
X
X} while (++tries < MAXTRIES);
X
Xif (tries >= MAXTRIES)
X	error("Timed out waiting for server");
X
Xtries = 0;
Xsprintf(buf, "%sfifow%d", FIFO, mypid);
X
Xdo {
X	if ((inport = open(buf, O_RDONLY)) == -1)
X		if (errno != ENOENT)
X			error("Couldn't open port from server");
X		else {
X			sleep(1);
X			continue;
X		}
X	else
X		break;
X
X} while (++tries < MAXTRIES);
X
Xif (tries >= MAXTRIES)
X	error("Timed out waiting for server");
X
Xserver_connected = TRUE;
Xwerase(remote);
Xwrefresh(remote);
X}
X
X
X
Xvoid	error(char *msg)
X{
Xwaddstr(remote, msg);
Xwaddch(remote, '\n');
Xwrefresh(remote);
Xsleep(3);
Xexit_prog();
X}
X
X
X
Xvoid	exit_prog()
X{
Xif (server_connected) {
X	message("\n[Disconnecting from server]");
X	write(outport, "QUIT\n", 5);
X	sleep(2);
X}
Xclear();
Xrefresh();
Xendwin();
Xif (readpid) {
X	kill(readpid, 9);
X	wait((int *)0);
X}
Xif (writepid) {
X	kill(writepid, 9);
X	wait((int *)0);
X}
X
Xmsgctl(msqid, IPC_RMID, 0);
X
Xexit(0);
X}
X
X
X
Xvoid	spawn_children()
X{
Xint		mypid;
X
X/* first child reads from the server, writes it into the message queue */
X
Xif ((readpid = fork()) == 0) {
X	char	buf[BUFSIZ];
X	int		cnt;
X	
X	setpgrp();
X	mypid = getpid();
X	while (1) {
X		cnt = read(inport, buf, BUFSIZ);
X		add_queue(buf, mypid, cnt);
X	}
X}
X
X/* second child reads from the keyboard, writes it into the message queue */
X
Xif ((writepid = fork()) == 0) {
X	char	c;
X
X	setpgrp();
X	mypid = getpid();
X	while (1) {
X		read(0, &c, 1);
X		if (c == '\r')
X			c = '\n';
X		add_queue(&c, mypid, 1);
X	}
X}
X}
X
X
X/* place a buffer into the message queue.  pid is the message type */
X
Xvoid	add_queue(char *buf, int pid, int cnt)
X{
Xmsg_t	msg;
X
Xmsg.mtype = pid;
Xmemcpy(msg.msg, buf, cnt);
X
Xif (msgsnd(msqid, &msg, cnt, 0) == -1)
X	error("msgsnd failed");
X}
X
X
X/* read the message queue and process the data.  parent process main loop */
X
Xvoid	read_queue()
X{
Xmsg_t	msg;
Xint		cnt;
X
Xwhile (1) {
X	if (clock_ticked)
X		update_clock();
X	
X	if ((cnt = msgrcv(msqid, &msg, sizeof(msg.msg), 0, 0)) == -1)
X		if (errno == EINTR)
X			continue;
X		else
X			error("msgrcv failed");
X
X	/* messages from the read process go to the remote window, messages
X	 * from the keyboard process go to the local window
X	 */
X		
X	if (msg.mtype == readpid || msg.mtype == 1) {
X		if (cnt < 1) {
X			server_connected = FALSE;
X			error("\n*** Lost contact with server -- Bye! ***");
X		}
X		process_output(msg.msg, cnt, msg.mtype);
X	} else if (msg.mtype == writepid) {
X		process_input(msg.msg[0]);
X	} else {
X		error("Unknown message type received");
X	}
X}
X
X}
X
X
X
Xvoid	message(char *msg)
X{
Xwaddstr(remote, msg);
Xwaddch(remote, '\n');
Xwrefresh(remote);
X}
X
X
X/* format keyboard input.  handle backspace/kill keys.  do word wrapping.
X */
X
Xvoid	process_input(char c)
X{
Xstatic	char	buf[BUFSIZ], *cptr = buf, *thisline = buf;
Xstatic	int		lastspace = -1, col = 0;
Xint				cur_row, cur_col;
Xchar			*ptr;
Xstatic	char	valid[] = { '\b', '\n', CTL('L'), CTL('U') };
X
Xif (c > 127)
X	return;
X
Xif (c < 32 && (strchr(valid, c) == NULL))
X	return;
X
Xswitch (c) {
X	case '\b':								/* backspace/delete */
X	case 127:
X		if (cptr > buf) {
X			cptr--;
X			getyx(local, cur_row, cur_col);
X			if (--col < 0)
X				col = 0;
X			mvwdelch(local, cur_row, --cur_col);
X			
X			if (col <= lastspace) {
X				*cptr = 0;
X				if ((ptr = strrchr(thisline, ' ')) != NULL)
X					lastspace = ptr - thisline;
X				else
X					lastspace = -1;
X			}
X		}
X		break;
X	
X	case CTL('U'):							/* cancel */
X		if (cptr > buf) {
X			cptr = buf;
X			waddstr(local, "\n[Cancelled]\n");
X			lastspace = -1;
X			thisline = buf;
X			col = 0;
X		}
X		break;
X		
X	case '\n':								/* line complete */
X		*cptr++ = c;
X		*cptr = 0;
X		
X		/* special message number '1' is an echoed command */
X		
X		if (echo_cmds)
X			add_queue(buf, 1, strlen(buf));
X		else		/* add blank line between server responses */
X			add_queue(&c, readpid, 1);
X
X		waddch(local, c);
X		write(outport, buf, strlen(buf));
X		cptr = buf;
X		thisline = buf;
X		lastspace = -1;
X		col = 0;
X		break;
X		
X	case CTL('L'):							/* redraw screen */
X		endwin();
X		doupdate();
X		break;
X		
X	case ' ':								/* mark spaces for word wrap */
X		lastspace = col;
X		/* no break -- INSERT NOTHING HERE */
X		
X	default:								/* all other chars */
X		*cptr++ = c;
X		col++;
X		if ((col >= COLS) && (lastspace > 0)) {
X			getyx(local, cur_row, cur_col);
X			wmove(local, cur_row, lastspace);
X			wclrtoeol(local);
X			waddch(local, '\n');
X			*cptr = 0;
X			thisline += lastspace + 1;
X			waddstr(local, thisline);
X			col = cptr - thisline;
X			lastspace = -1;
X		} else
X			waddch(local, c);
X
X		break;
X}
X
Xwrefresh(local);
X}
X
X
X/* process output from server.  do word wrapping.  highlight 'special'
X * responses.
X */
X
Xvoid	process_output(char *buf, int cnt, int hilite)
X{
Xchar	*ptr;
Xint		unbold = FALSE;
X
Xbuf[cnt] = 0;
X
Xreformat(buf);
Xwhile (ptr = reformat(NULL)) {
X	if (special(ptr)) {						/* pages, etc. */
X		unbold = TRUE;
X		wattron(remote, A_REVERSE);
X	}
X	
X	if (hilite == 1) {						/* echoed commands */
X		unbold = TRUE;
X		wattron(remote, A_UNDERLINE);
X	}
X	
X	waddstr(remote, ptr);
X	
X	if (unbold)
X		wstandend(remote);
X}
X
Xwnoutrefresh(remote);
Xwnoutrefresh(local);					/* put cursor back on local window */
Xdoupdate();
X}
X
X
X/* reformat a null-terminated string containing possibly several newlines.
X * return a series of newline/null terminated strings which fit on the
X * screen.
X */
X
Xchar	*reformat(char *buf)
X{
Xstatic	char	retbuf[BUFSIZ+1], holdbuf[2*BUFSIZ+1], *baseptr;
Xchar	*cptr;
Xstatic	int		leftovers = FALSE;
X
X/* initial call.  save the user's string for further processing and return.
X */
X
Xif (buf) {
X	if (leftovers)					/* unfished line from last call? */
X		strcat(holdbuf, buf);
X	else
X		strcpy(holdbuf, buf);
X	baseptr = holdbuf;
X	leftovers = FALSE;
X	return NULL;
X} else if (baseptr[0] == '\0')
X	return NULL;
X
Xif ((cptr = strchr(baseptr, '\n')) == NULL) {		/* no newline */
X	strcpy(holdbuf, baseptr);
X	leftovers = TRUE;
X	return NULL;
X}
X
Xif (cptr - baseptr < COLS) {					/* this line fits */
X	memcpy(retbuf, baseptr, cptr - baseptr + 1);
X	retbuf[cptr - baseptr + 1] = 0;
X	baseptr = cptr + 1;
X	return retbuf;
X}												/* this line doesn't fit */
X
Xcptr = &baseptr[COLS-1];
X
Xwhile ((*cptr != ' ') && (cptr > baseptr))		/* find last space */
X	cptr--;
X
Xif (cptr == baseptr) {							/* no space found */
X	memcpy(retbuf, baseptr, COLS-1);
X	retbuf[COLS-1] = '\n';
X	retbuf[COLS] = 0;
X	baseptr += COLS - 1;
X	return retbuf;
X}
X
X/* space found */
X
Xmemcpy(retbuf, baseptr, cptr - baseptr);
Xretbuf[cptr - baseptr] = '\n';
Xretbuf[cptr - baseptr + 1] = 0;
Xbaseptr = cptr + 1;
Xreturn retbuf;
X
X}
X
X
X
X/* alarm interrupt handler */
X
Xvoid	clock_tick()
X{
Xsignal(SIGALRM, clock_tick);
Xclock_ticked = TRUE;
Xalarm(clock_freq);
X}
X
X
X
X/* update the status window clock display */
X
Xvoid	update_clock()
X{
Xtime_t	current = time(NULL);
Xtime_t	elapsed = current - start_time;
Xtime_t	hours = elapsed / 3600;
Xtime_t	mins = (elapsed - (hours * 3600)) / 60;
Xtime_t	secs = elapsed % 60;
Xchar	*cptr = ctime(&current);
X
Xcptr[19] = 0;
X
Xmvwprintw(status, 0, COLS/4-5, "[%02d:%02d:%02d]", hours, mins, secs);
Xmvwprintw(status, 0, 3*COLS/4-5, "[%s]", &cptr[11]);
X
Xwnoutrefresh(status);
Xwnoutrefresh(local);
Xdoupdate();
X
Xclock_ticked = FALSE;
X}
X
X
X/* returns true if we want to highlight this line */
X
X
Xint		special(char *buf)
X{
Xreturn strstr(buf, "pages:") == NULL ? 0 : 1;
X}
X
X
X
X/*	Find first occurence of str2 in str1 and return a pointer to it */
X
Xchar *strstr(str1, str2)
Xchar *str1, *str2;
X{
X	register char *Sptr, *Tptr;
X	int len = strlen(str1) -strlen(str2) + 1;
X
X	if (*str2)
X		for (; len > 0; len--, str1++) {
X			if (*str1 != *str2)
X				continue;
X
X			for (Sptr = str1, Tptr = str2; *Tptr != '\0'; Sptr++, Tptr++)
X				if (*Sptr != *Tptr)
X					break;
X
X			if (*Tptr == '\0')
X				return str1;
X		}
X
X	return NULL;
X}
SHAR_EOF
$TOUCH -am 0308102291 tmc.c &&
chmod 0666 tmc.c ||
echo "restore of tmc.c failed"
set `wc -c tmc.c`;Wc_c=$1
if test "$Wc_c" != "11599"; then
	echo original size 11599, current size $Wc_c
fi
exit 0