atom4/DESIGN0100600000175000017500000000242407577220365012354 0ustar hsteohhsteoh Triangular board representation ------------------------------- Store as an W*H array, where each position represents a vertex on the board's triangular divisions. The odd-numbered rows are shifted half a position to the right of the even-numbered rows, and the rows are spaced so that the resulting vertex positions actually form equilateral triangles. Under this arrangement, given a point (x,y) in the array, the actual location of the vertex is (x + (y%2)/2, y). To avoid confusion, we call the former "board coordinates" and the latter "actual coordinates". In board coordinates, the neighbours of any given point (x,y) are: For even y: (x-1, y-1), (x, y-1) (x-1, y), (x+1, y) (x-1, y+1), (x, y+1) For odd y: (x, y-1), (x+1, y-1) (x-1, y), (x+1, y) (x, y+1), (x+1, y+1) The difference matrix between these two cases is: [ (1,0) (1,0) ] DM = [ (0,0) (0,0) ] [ (1,0) (1,0) ] I.e., for even y, we use the formulae given above, for odd y, we add DM to the formulae for even y. On every vertex in the board, there are 6 cardinal directions, corresponding to the 6 vertices surrounding it. For convenience, we number them clockwise starting from the vertex to the upper left. Each cardinal direction corresponds, obviously, to each of the 6 neighbouring vertices described above. atom4/README0100600000175000017500000002046607634107707012344 0ustar hsteohhsteoh------------------------------------------------------------------------------ ATOM-4 by H. S. Teoh Dec 2002 ------------------------------------------------------------------------------ INTRODUCTION Atom-4 is a 2-player color manipulation game played with spherical pieces on a board divided into equilateral triangles. The player who first makes a row of 4 pieces of the right color wins. RULES Pieces may be placed only on the vertices of the triangular game board divisions, and only if touching two other pieces which themselves are adjacent to each other (i.e., it must form an equilateral triangle with two adjacent pieces already on the board). Theoretically, the board is unlimited in size; practically, we limit it to 16 vertices across and 16 rows down. Pieces have 8 different colors in total, grouped into 4 groups: - black - red, green, and blue (the primary, or "additive", colors) - yellow, cyan, and purple (the secondary, or "subtractive", colors) - white Black and white are also called "propagators" (explained below). The first player plays additive colors, and must make a row of 4 whites. White is the "goal piece" of the first player. Similarly, the second player plays subtractive colors, and must make a row of 4 blacks. Black is the "goal piece" of the second player. Since neither player can play their goal pieces directly, they need to combine the colors they play in order to form their goal pieces on the game board, indirectly. Whenever an additive or subtractive piece is put on the board, it changes the color of pieces surrounding it. The color changes are illustrated by the following color wheel: red yellow \ / purple --*-- green / \ blue cyan 1) If the neighbouring piece has an adjacent color on the wheel, it does not change. For example, if red is placed next to yellow or purple, the yellow or purple remains the same. 2) If the neighbouring piece has a color 60 degrees away on the wheel, then it changes to the color in between. For example, if red is placed next to green, the green turns into a yellow. If a red is placed next to a blue, the blue turns purple. 3) If the neighbouring piece has the opposite color on the wheel, then it changes to either white or black, depending on what type of color the new piece is. If the new piece is an additive piece, the neighbour becomes white; if it is a subtractive piece, the neighbour becomes black. For example, if a red is placed next to a cyan, the cyan turns white; but if a cyan is placed next to the red, the red turns black. 4) If the new piece is additive and the neighbouring piece is black, then the black changes to the same color as the new piece. Similarly, if the new piece is subtractive and the neighbouring piece is white, then the white changes to the same color as the new piece. 5) If the new piece is additive and the neighbouring piece is white, then the white does not change, but the color change effect "propagates" through the white to the piece behind the white. That piece then changes as though the new piece had been placed next to it. If it is also white, then the effect continues propagating in the same direction, in a straight line, until it reaches a non-white piece, and then changes that non-white piece as though the new piece was placed next to it. If an empty spot is reached before a non-white piece, then nothing happens. Because of this effect, white pieces are also called "additive propagators". 6) Similarly, if the new piece is subtractive and the neighbouring piece is black, the color change effect propagates in the direction of the black until it reaches a non-black piece, which then changes as though the new piece had been placed next to it. Nothing happens if an empty spot is reached before a non-black piece. Hence, black pieces are also called "subtractive propagators". (Another way to understand the color changes is treat colors as red, green, and blue combinations. Additive colors always try to "add" themselves to their neighbours: red + green = yellow (red & green together); red + cyan (green & blue) = white. Subtractive colors try to remove their complement color from their neighbours. For example, the complement of yellow (red & green) is blue; so yellow tries to remove blue from its neighbours. Hence, when yellow (red & green) is placed next to cyan (green & blue), the cyan turns green (loses the blue component). Similarly, when cyan (green & blue) is placed next to white (red & green & blue), it removes its complement, red, from the white; so the white becomes cyan as well. In other words, additive colors behave like colored light, whilst subtractive colors behave like colored paint.) The initial state of the board consists of two pieces, green and purple, in the middle of the board, touching each other. The first player then plays a red, the second player plays a yellow, and then the first player plays a green, and so on, taking turns, going clockwise around the color wheel. The first person to make a row of 4 propagators wins. If the game is played in multiple rounds, the second player may start first on the second round, using a subtractive piece, and then the first player with the next color clockwise on the color wheel, and so on. The starting configuration always consists of two pieces, one 30 degrees counterclockwise from the starting color on the color wheel, and the other 60 degrees clockwise; each touching the other in the center of the board. GAME STRATEGY CONSIDERATIONS 1) Notice that in order to get from additive colors to white, the first player must form secondary colors and then add their complements; but the second player already plays secondary colors. So the first player can make use of the pieces played by the second player to make whites, which is faster than building whites from scratch. Similarly, the second player plays subtractive pieces and must first form primary colors and then add the complements to make black; but the first player already plays primary colors, which can be exploited to make blacks. This also means that when playing a piece, one should be careful not to give too much advantage to the other player by providing material to make propagators (black or white). 2) Propagators (blacks or whites) are useful for changing colors of pieces already blocked from direct access because they are surrounded by other pieces. Using propagators, you can create more propagators from such "buried" pieces. Strategic positioning of propagators that allow you to reach these "internal" pieces is key to winning the game. On the other hand, one should also be aware that the opponent can destroy such exposed propagators easily. 3) Since it is relatively easy for one's opponent to prevent one from winning by changing the color of a piece intended to be the 4th propagator in the row of 4, the good strategy is to devise a way to have at least two different pieces that can serve as a 4th piece in the row. Another good strategy is to bury the prospective 4th piece with other non-essential pieces so that the opponent cannot easily change its color, and have multiple propagator paths to it. Then if the opponent blocks one propagator path, another one is available to reach it. 4) It is very useful to anticipate the color of one's subsequent piece, and plan accordingly. For example, if the first player is playing a red, and there are no cyans around, it is useful to place the red next to blue pieces, because they form purple which can be complemented by the green on the next turn. If they are placed next to green pieces, the result is yellow, which cannot be used until 2 turns later. COPYRIGHT This game concept and its accompanying software implementation thereof is copyrighted 2002-2003 by Hwei Sheng Teoh . This software is licensed under the General Public License. The terms and conditions of this license can be found in the COPYRIGHT file shipped with the source code. If you are using a Debian GNU/Linux system, the license may be found in /usr/share/common-licenses/GPL-2. A copy of the license may also be obtained from the Free Software Foundation website, at: http://www.gnu.org/licenses/gpl.txt atom4/Construct0100600000175000017500000000635507647075677013412 0ustar hsteohhsteoh#!/usr/bin/cons # # Atom-4 build script. Requires cons, in case you haven't clued in yet. # # Configurable parameters: (run with 'cons $VARNAME=$VALUE ...') # # BINDIR=$dir Where to install Atom-4 binaries # COMPILER=$binary Compiler to use (default: g++) # DATADIR=$dir Where data files should be installed # MANDIR=$dir Directory to store manpages # REALDATADIR=$dir Where Atom-4 should look for data files (default # is $DATADIR; this option is mainly for packaging # builds where the build-time dir is different from # the run-time dir) # PROGLIBPATH=$dir Path to prog/lib libraries (default: #../lib) # X11LIBPATH=$dir Where to find X11 libraries (default: /usr/X11R6/lib) # DEBUG=1 Build with debugging symbols # OPTIMIZE=$n Optimize to level $n. Set to 0 to disable. # (Default: 2) # PROFILE=1 Build with profiling code (for development use only) # # # I *love* cons. Make is just an idiotic overly-bandaged contraption way past # its lifetime. Everyone should use Cons for all new products! It will save # you countless hours, days, weeks, of endless build frustrations. I swear # I'll never, ever, use Make again. It's about time it died a long overdue # death. # # # $Id: Construct,v 1.16 2003/04/15 13:47:09 hsteoh Exp hsteoh $ # # # Useful routines # # abspath $relpath sub abspath { my $path=shift; my $pwd=`pwd`; chomp $pwd; $path=~s#^#$pwd/# unless $path=~m#^/#; return $path; } # # Cons setup # # Chdir while processing subdir Conscripts Conscript_chdir 1; Export qw( BINDIR DATADIR INCDIR LIBDIR MANDIR OBJDIR PROGLIB PROGLIBPATH NCURSESLIB X11LIB X11LIBPATH CONS COMPILER CFLAGS ); # External configuration (can be overridden by running Cons with the # appropriate X=Y W=Z ... options) $DATADIR = $ARG{DATADIR} || '#data'; $OPTIMIZE = defined $ARG{OPTIMIZE} ? $ARG{OPTIMIZE} : 2; $PROFILE = $ARG{PROFILE} || 0; # Shared files $BINDIR = $ARG{BINDIR} || '#bin'; $INCDIR = '#include'; $MANDIR = $ARG{MANDIR} || '#man'; $LIBDIR = '#lib'; $OBJDIR = '#obj'; # Locations of required libs $PROGLIB = '-lt++'; $PROGLIBPATH = $ARG{PROGLIBPATH} || '#../lib'; $NCURSESLIB = '-lpanel -lncurses'; $X11LIB = '-lX11 -lXpm'; $X11LIBPATH = $ARG{X11LIBPATH} || '/usr/X11R6/lib'; # Global configuration $COMPILER = $ARG{COMPILER} || 'g++'; $REALDATADIR = $ARG{REALDATADIR} || abspath(DirPath($DATADIR)); $CFLAGS = "-pedantic -DDATADIR=\\\"$REALDATADIR\\\""; $CFLAGS .= " -g3" if $ARG{DEBUG}; $CFLAGS .= " -O$OPTIMIZE" if $OPTIMIZE; $CFLAGS .= " -pg -DPROFILE" if $PROFILE; # Local configuration $INCPATH = "$INCDIR:$PROGLIBPATH/include"; $LIBPATH = "$PROGLIBPATH/lib:$LIBDIR:$X11LIBPATH"; $LIBS = "$PROGLIB $NCURSESLIB $X11LIB -latom4 -lxatom4"; $LIBS .= " -pg" if $PROFILE; $CONS = new cons( CC => $COMPILER, CFLAGS => $CFLAGS, CPPPATH => $INCPATH, LIBS => $LIBS, LIBPATH => $LIBPATH ); # Build tree Build qw( engine/Conscript general/Conscript ncurses/Conscript net/Conscript x/Conscript ); # # Headers # Install $CONS $INCDIR, 'interface.h'; # # Main program # Install $CONS $BINDIR, 'atom4'; Program $CONS 'atom4', 'atom4.cc', 'interface.cc', "$OBJDIR/event.o", "$OBJDIR/textui.o"; # # Auxilliary stuff # Install $CONS "$MANDIR/man6", 'atom4.6'; atom4/atom4.cc0100600000175000017500000000660607633651736013024 0ustar hsteohhsteoh/* * Atom-4 main program * --------------------------------------------------------------------------- * $Id: atom4.cc,v 1.10 2003/03/12 15:35:50 hsteoh Exp hsteoh $ */ #include #include #include #include // for time() #include // for getopt() #include "ai.h" #include "event.h" #include "exception.h" #include "game.h" #include "interface.h" #include "textui.h" #include "xatom4.h" #include "xutil.h" #define BOARD_WIDTH 16 #define BOARD_HEIGHT 16 enum game_mode { AUTO_MODE=0, NCURSES_MODE, X11_MODE }; void seed_rand() { srand((unsigned int)time(NULL)); } interface *init_ui(game_mode mode, int iface_opts, atom4 *engine, eventloop *loop, int *exitflag) { // [O]([R],[R]) switch (mode) { case X11_MODE: // NULL = use $env{DISPLAY} return new x11ui(NULL, engine, loop, exitflag); case NCURSES_MODE: return new ncurses_ui(engine, loop, exitflag, iface_opts); default: fprintf(stderr, "Internal error: unknown interface mode\n"); exit(-1); } } void display_help() { fprintf(stderr, "Syntax: atom4 [options]\n" "Options:\n" " -a Enable AI player as player \n" " -c [NCURSES] Enable color (if your terminal supports it)\n" " -d [AI] Set difficulty level (0=easiest, 2=default (harder), ...)\n" " -h Show this help\n" " -mt Use text mode (NCURSES)\n" " -mx Use X11 mode\n" ); exit(1); } int main(int argc, char *argv[]) { game_mode mode=AUTO_MODE; int iface_opts=0, ai_player=0, ai_level=2; int ch; while ((ch=getopt(argc, argv, "a:cd:hm:")) != -1) { switch (ch) { case 'a': if (*optarg=='1') ai_player=1; else if (*optarg=='2') ai_player=2; else { fprintf(stderr, "Bad AI player spec: %s\n", optarg); exit(1); } break; case 'c': iface_opts |= ncurses_ui::ENABLE_COLOR; break; case 'd': ai_level = atoi(optarg); break; case 'm': if (!strcmp(optarg, "t")) { mode = NCURSES_MODE; } else if (!strcmp(optarg, "x")) { mode = X11_MODE; } else { fprintf(stderr, "Unknown mode spec: %s\n", optarg); exit(1); } break; case '?': case 'h': default: display_help(); // never returns } } // Auto mode: run X11 interface if $DISPLAY is set, otherwise run ncurses // interface. if (mode == AUTO_MODE) { char *xdisplay = getenv("DISPLAY"); mode = (xdisplay) ? X11_MODE : NCURSES_MODE; } // Initialize game engine & event loop try { eventloop mainloop; atom4 *game_engine; seed_rand(); if (ai_player) { fprintf(stderr, "Initializing AI as player %d\n", ai_player); game_engine = new atom4ai(&mainloop, BOARD_WIDTH, BOARD_HEIGHT, ai_player); fprintf(stderr, "Setting AI difficulty as %d\n", ai_level); ((atom4ai*)game_engine)->set_search_depth(ai_level); } else { game_engine = new atom4local(BOARD_WIDTH, BOARD_HEIGHT); } int exitflag=0; interface *ui = init_ui(mode, iface_opts, game_engine, &mainloop, &exitflag); // Main loop try { mainloop.run(&exitflag); } catch(...) { delete ui; throw; } delete ui; } catch(exception &e) { fprintf(stderr, "Exception: %s\n", e.message()); exit(1); } fprintf(stderr, "Exiting normally\n"); return 0; } atom4/interface.h0100600000175000017500000000053607647076505013576 0ustar hsteohhsteoh/* * Atom-4 UI base class * Header file * * $Id: interface.h,v 1.2 2003/04/15 21:44:04 hsteoh Exp hsteoh $ */ #ifndef INTERFACE_H #define INTERFACE_H #include "game.h" #define VERSION_STRING "v4.1" class interface { protected: atom4 *game; // [R] public: interface(atom4 *engine); virtual ~interface(); }; #endif // INTERFACE_H atom4/interface.cc0100600000175000017500000000035007623337044013716 0ustar hsteohhsteoh/* * Atom-4 UI base class * Implementation file * * $Id: interface.cc,v 1.1 2003/02/15 04:11:50 hsteoh Exp hsteoh $ */ #include "interface.h" interface::interface(atom4 *engine) : game(engine) {} interface::~interface() {} atom4/NOTES0100600000175000017500000000112407645261316012264 0ustar hsteohhsteohNotes on profiling AI: From: Ian Lance Taylor (ian@zembu.com) [...] > >Fork is easy enough to handle, at least on GNU/Linux. If you set the >environment variable GMON_OUT_PREFIX, glibc will automatically append >the process ID when writing the gmon.out file. If you then also do >this in the child after calling fork: > extern void _start (void), etext (void); > monstartup ((u_long) &_start, (u_long) &etext); >you will a set of ${GMON_OUT_PREFIX}.pid files, one for the parent and >one for each child. Note: set GMON_OUT_PREFIX to be the desired filename prefix for the gmon.out files. atom4/TODO0100600000175000017500000002705007647075264012156 0ustar hsteohhsteoh- OK, this is it. It's time for a MAJOR rewrite of the game engine interface. The current design of b0rken inheritance hierarchy is starting to cause nightmares with the network code. - players and game engines should be separate entities. Game engines contain players, but players (i.e. the current AI player) is NOT a "type of" game engine. Should have a separate player class which can then contain stuff like scores, nicknames, etc.. - players and interfaces are distinct things; the "human" player should simply be derived from a base player class in such a way that it connects to the interface. So XAtom-4 has an X11 player, textui has an ncurses player, etc.. Similarly, the AI player is its own player. Network players will just be another type of player. Interfaces should know how to work with any player type (the specialized player type for that interface can simply be an "active" class hooked off eventloop that triggers state changes in the interface; no need for special handling in the interface class itself). - game state change notification should also be expanded to cover events like round over, change of turn, etc.. Interfaces shouldn't have to keep on examining game state (although that should certainly be possible too). - Add history and playback capability - need network play! :-) - all the infrastructure is in place, just have to actually implement the network communications layer. + IDEA: + then attach textui to a fake game engine which is actually a network client that connects to the server. We'll have to change make_move() so that it will know to wait for a network response instead of asking the user for a response. (Although now it's arguable whether this should be in class interface instead of something else...) X replace textui with a network interface, which basically acts like a game server; + alternatively: + have textui able to choose between the real engine and a network engine. + textui will have two modes: standalone mode, where it gets input for both users, and network mode, where it gets input for one player from console, and the other from network. - AI: profile a search, and see if we can't optimize it by caching hdelta's. * actually, hdelta's are the least of your worries. The current big CPU hog appears to be find_legal_moves(). (Unsurprising; it has to scan the entire board to determine legal moves, and checks 6 neighbours per cell.) - implement permanent AI subprocess first! We can't cache anything if we're fork()ing each turn. We should probably just implement network play and have the AI communicate over the network layer. No need for yet another interface when one would do! - AI: make pcreated, pdestroyed weights configurable. - AI: add optional delay, so that we can (potentially) pit two AI's against each other :-) - more helpful failure codes from game::move(). Preferably we should standardize messages in the game engine instead of relying on the interface. + need to mark up board with coors so that players can see where to move; or alternatively, cycle ncurses cursor through all legal moves. + need to reset last_x,last_y and clear messages pane after restarting new game. + add color to pieces :-) + BUG: board window is one column smaller than it should be ('cos odd rows are shifted right, and require one more column to fit!) + should clearly indicate which color is which player (display player headings in the appropriate color!) + CRITICAL BUG: check_win() does NOT correctly detect wins if the last marble placed is in the center of the row of 4 instead of either end. X split off base class interface and subclass textui... then we can have a network interface, e.g. * Unnecessary. Just write a new UI. There is no compelling reason to use the same base class anyway, since the only public method is run()! X need to have independent player class (for scorekeeping and for keeping track of colors, etc) -- although maybe it's good enough to keep this in class interface. * scorekeeping is now in the game engine, as it should be. X need to clean up board representation; ideally, EMPTY_CELL should = 0, and BAD_CELL should = -1, and players' pieces should be stored as the player number. * Unnecessary. + implement new version of game, just to get a proof-of-concept of the new 2-player, 3-colors idea. * Hmm, it's harder to balance than I thought. Because of the way the colors are alternated, whoever lays down the N-1'th piece in a row will almost certainly lose; so there's not much motivation except to play defensive, in which case it's impossible to win. * Sounds like in order for this to be plausible, we're gonna have to explore the 8-color scheme. + need to implement a true multiplayer interface for game engine + game engine will be modal: DUAL (current), PEER (interface represents only a single player), WATCH (only receiving updates, not playing). + fix X11 interface to support modal engines! X should separate client from interface, maybe? X another idea: have game engine require two players to "plug in"; but we can connect the same interface to the engine twice to get the current setup + generalize class interface to allow integration with X11 client. + Clean up class atom4local mess!!! + should inherit from triboard and put all color-manipulation stuff in there. X Probably the best way is to templatize class triboard. + implement copy ctor for class triboard, so that it's easier to implement state-space search algos for the AI. + fix -a so that we can specify which player AI should be. + Implement AI player! It's a tad boring to play against myself :-P + can have configurable AI, assigning different weights to the following: + single-depth scores: + number of colors a move changes + number of propagators a move creates + number of opponent propagators a move destroys X number of opponent propagators vs. number of our propagators in resultant state space * Probably not necessary + recursive state-space searching scores: + even depths (opponent's turn): + losing move: if opponent has a winning move, assign negative score + (recursive) if all searched sub-moves result in states where opponent has a winning move, assign negative score + pruning: + instead of recursively considering all possible opponent moves, only consider those we would pick if we were in that position + winning move: if a move causes a win, pick it for sure! :-) + implemented min-max algorithm. Strangely, it seems quite inferior in terms of AI defense/offense (and takes a lot longer too!). + AI: implement a subprocess for the AI! that's what we have event loops for! + update README to explain the new 8-color game! :-) + [ncurses] should be updated to handle new 8-color system. + [ncurses] should be updated to support modal engines + make it easy to customize construction vars like PREFIX, DATADIR, etc. * can just use Cons' %ARG feature. + BUG: fix varexception so that catch(exception) will work properly!!!! + it shouldn't just work for catch(exception&) + we should probably merge the two into the base class anyway, so that we can do exception class hierarchies in a sane way. + BP: ambiguous prototypes exception(char*) and exception(char*, ...) * one solution may be to use the first char of the message as indicator (start with '@' for varexceptions). This should be minimally ugly, at least better than introducing a new parameter! + BUG: atom4 cannot be suspended with SIGSTOP; upon resume, event loop dies with Interrupted Syscall exception. (Should probably catch this condition, since it is something to be expected. Also, should probably make sure we don't die if 0 fd's are selected, which would happen when the process resumes after a SIGCONT.) + BUG: fix game engine so that it will detect stalemate (board filled up with no winners). This may be rare, but you don't want to be embarrassed when somebody actually manages to achieve that!! * Simplest way is just to maintain a counter initialized with w*h and decremented every time a non-blank piece is placed. + Write a tarball-building script! It's quite a pain to do it by hand each time. + Update the Debian package with the eventloop fix. X [ncurses] need to handle SIGWINCH. * May not need to after all; ncurses-dev comes with its own SIGWINCH handler. + [ncurses] fix non-updating board background on PuTTY. + AI: why is min-max algorithm performing so poorly?? * 'cos you did it wrong, dummy! You're supposed to evaluate the heuristic function only at leaf nodes. Otherwise the min-max propagation becomes meaningless, since it would effectively yield the path that leads to the best move (in terms of the color changes heuristic), rather than the path to the best game state. * somehow, the heuristic algorithm seems to perform best at depth=2. Trying depth=4 doesn't yield much improvement at all, and is just boringly slow. + implement REAL min-max algo!!! + IDEA: still base heuristic score on per-piece changes, but accumulate them in a struct, only evaluate them at leaf nodes. Then propagate them back using a real min-max selector. + this way, we can actually implement alpha-pruning...! + BBBBP: min-max algo still seems to perform horribly poorly. :-/ * Because you did it wrong again you dolt!!!! You forgot to invert hdelta between turns, so enemy advances were counted as gains!!! You fool!!!! + argh, something is SERIOUSLY wrong with your implementation!! It's not even detecting losing moves. X Must be that max=-min parameter, I think... * argh, something's wrong, it's not even detecting winning states... + Aha, found problem: in pick_best_move(), we treat equal scores equally; but pruned subtrees return max, which may get equal score with another non-pruned subtree. This is OK in score_opponent_move() because we use > instead of >=. But pick_best_move() uses >=, so it misidentifies a pruned branch as a good move. * FIXED!! Let's see how well it performs now... + [ncurses] do a message("") after board change happens (AI moved), so that the "it's not your turn" message doesn't continue sticking there after it *is* your turn. Or better, print a message saying what the AI moved. + AI: min-max algo is *still* not doing very well at increased search depths. At least -d2 seems to be at the same level as the original heuristic algo. -d3 and -d4 seem to be a regression though :-( Must be a problem with the heuristic value function... + Ack! Looks like there's a bug in the score propagation code. Negative scores from AI's turn doesn't change sign when propagated to opponent's turn. * FIXED!! Now the AI is pretty formidable at -d3 !! + [ncurses] After AI moves, should restore cursor pos to current board pos, else it looks misleading and player may make a mistake. + BUG: fixed incorrect negation of hdelta in AI. AI seems to be acting more rationally now. :-P * Wow, -d3 is now quite hard! + BUG: event loop postponed adds/deletes are inconsistent when the same fd is added/deleted within a single dispatch cycle. Before appending a new entry to the added/deleted list, we should check if it's already in the other list and remove it from the other list if necessary. + eventloop: we REALLY need to implement timers now. Use gettimeofday() to get the same precision select() uses. + Add timer to event loop atom4/atom4.60100600000175000017500000003067407645037541012601 0ustar hsteohhsteoh.TH ATOM4 6 .\" Atom-4 manual page. .\" Just because Debian requires one. :-) .SH NAME atom4 \- two-player color puzzle game .SH SYNOPSIS .B atom4 [ \fB-a \fR\fIn\fR ] [ \fB-d \fR\fIlevel\fR ] [ \fB-mt\fR | \fB-mx\fR ] .br .B atom4 -h .SH DESCRIPTION Atom-4 is a two-player color manipulation game played with colored spherical pieces on a board divided into equilateral triangles. The player who first makes a row of 4 pieces of the right color wins. .PP There is an AI mode where you play against the computer. By default, .B atom4 runs in 2-player mode. Since 2-player mode is controlled from the same terminal, it can be used as a "practice" mode to acquiant oneself with the color change rules or to explore strategic possibilities in a controlled way. .PP .B atom4 supports both a curses-based text interface and an X11 interface. The interface can be selected with the .B -m option. By default, .B atom4 launches the X11 interface if the .B $DISPLAY environment variable is set, and the curses-based interface otherwise. .SH OPTIONS .TP .B -a \fIn\fR Play against AI player. \fIn\fR must be either 1 or 2, specifying which player the AI will be. .TP .B -d \fIn\fR Set AI player's difficulty level, where .I n is an integer from 0 or larger. The default difficulty setting is 2. This version of Atom-4 uses a real min-max algorithm; higher difficulty settings are actually much harder unlike in the previous version. However, be warned that very high difficulty settings will likely be very slow, as the game tree grows very quickly. .TP .B -h Shows a summary of command-line options that .B atom4 takes. .TP .B -mt Selects the text (curses-based) interface. The curses-based interface requires a terminal with color capabilities; at least 9 colors are needed. .TP .B -mx Selects the X11 interface. The X11 interface requires an X display that supports at least 8-bit color. Note that currently, .B atom4 will always connect to the X server specified in the .B $DISPLAY environment variable. .SH TEXT MODE INTERFACE The text mode interface requires a terminal that supports at least 9 colors. .PP The game controls are straightforward: the keypad arrow keys move the cursor around the board, and the Enter key or the Space key will place the piece being played on the board. The panel on the right shows you which piece is currently being played. Gameplay proceeds until one of the players win. .PP You can press .B q at any time to quit the game. .PP After one of the players win, the game will pause. You can either press .B n to proceed to the next round, or .B q to quit. .SH X11 INTERFACE The X11 interface requires an X display which has at least 8-bit color. .PP Gameplay on the X11 interface is simple: the color wheel in the right panel shows the order in which pieces are played, as well as the current player (number in the center). The current piece being played is highlighted in the color wheel. To play the piece, simply locate your mouse over the desired spot on the board and click the mouse button. .PP When it is your turn to play, and your mouse hovers over a legal position where you can place a piece, the piece you are currently playing will appear under the mouse cursor. It is not actually placed on the board until you click the mouse button. .PP At any time during the game, you may press .B q to quit the game. .PP After one of the players win, press .B n to proceed to the next round. .SH GAME RULES (Adapted from the README file.) .PP Pieces may be placed only on the vertices of the triangular game board divisions, and only if touching two other pieces which themselves are adjacent to each other (i.e., it must form an equilateral triangle with two adjacent pieces already on the board). Theoretically, the board is unlimited in size; practically, we limit it to 16 vertices across and 16 rows down. .PP Pieces have 8 different colors in total, grouped into 4 groups: - black - red, green, and blue (the primary, or "additive", colors) - yellow, cyan, and purple (the secondary, or "subtractive", colors) - white Black and white are also called "propagators" (explained below). .PP The first player plays additive colors, and must make a row of 4 whites. White is the "goal piece" of the first player. Similarly, the second player plays subtractive colors, and must make a row of 4 blacks. Black is the "goal piece" of the second player. .PP Since neither player can play their goal pieces directly, they need to combine the colors they play in order to form their goal pieces on the game board, indirectly. Whenever an additive or subtractive piece is put on the board, it changes the color of pieces surrounding it. The color changes are illustrated by the following color wheel: red yellow \\ / purple --*-- green / \\ blue cyan .HP 1) If the neighbouring piece has an adjacent color on the wheel, it does not change. For example, if red is placed next to yellow or purple, the yellow or purple remains the same. .HP 2) If the neighbouring piece has a color 60 degrees away on the wheel, then it changes to the color in between. For example, if red is placed next to green, the green turns into a yellow. If a red is placed next to a blue, the blue turns purple. .HP 3) If the neighbouring piece has the opposite color on the wheel, then it changes to either white or black, depending on what type of color the new piece is. If the new piece is an additive piece, the neighbour becomes white; if it is a subtractive piece, the neighbour becomes black. For example, if a red is placed next to a cyan, the cyan turns white; but if a cyan is placed next to the red, the red turns black. .HP 4) If the new piece is additive and the neighbouring piece is black, then the black changes to the same color as the new piece. Similarly, if the new piece is subtractive and the neighbouring piece is white, then the white changes to the same color as the new piece. .HP 5) If the new piece is additive and the neighbouring piece is white, then the white does not change, but the color change effect "propagates" through the white to the piece behind the white. That piece then changes as though the new piece had been placed next to it. If it is also white, then the effect continues propagating in the same direction, in a straight line, until it reaches a non-white piece, and then changes that non-white piece as though the new piece was placed next to it. If an empty spot is reached before a non-white piece, then nothing happens. Because of this effect, white pieces are also called "additive propagators". .HP 6) Similarly, if the new piece is subtractive and the neighbouring piece is black, the color change effect propagates in the direction of the black until it reaches a non-black piece, which then changes as though the new piece had been placed next to it. Nothing happens if an empty spot is reached before a non-black piece. Hence, black pieces are also called "subtractive propagators". .PP (Another way to understand the color changes is treat colors as red, green, and blue combinations. Additive colors always try to "add" themselves to their neighbours: red + green = yellow (red & green together); red + cyan (green & blue) = white. Subtractive colors try to remove their complement color from their neighbours. For example, the complement of yellow (red & green) is blue; so yellow tries to remove blue from its neighbours. Hence, when yellow (red & green) is placed next to cyan (green & blue), the cyan turns green (loses the blue component). Similarly, when cyan (green & blue) is placed next to white (red & green & blue), it removes its complement, red, from the white; so the white becomes cyan as well. In other words, additive colors behave like colored light, whilst subtractive colors behave like colored paint.) .PP The initial state of the board consists of two pieces, green and purple, in the middle of the board, touching each other. The first player then plays a red, the second player plays a yellow, and then the first player plays a green, and so on, taking turns, going clockwise around the color wheel. The first person to make a row of 4 propagators wins. .PP If the game is played in multiple rounds, the second player may start first on the second round, using a subtractive piece, and then the first player with the next color clockwise on the color wheel, and so on. The starting configuration always consists of two pieces, one 30 degrees counterclockwise from the starting color on the color wheel, and the other 60 degrees clockwise; each touching the other in the center of the board. .SH GAME STRATEGY CONSIDERATIONS Notice that in order to get from additive colors to white, the first player must form secondary colors and then add their complements; but the second player already plays secondary colors. So the first player can make use of the pieces played by the second player to make whites, which is faster than building whites from scratch. Similarly, the second player plays subtractive pieces and must first form primary colors and then add the complements to make black; but the first player already plays primary colors, which can be exploited to make blacks. .PP This also means that when playing a piece, one should be careful not to give too much advantage to the other player by providing material to make propagators (black or white). .PP Propagators (blacks or whites) are useful for changing colors of pieces already blocked from direct access because they are surrounded by other pieces. Using propagators, you can create more propagators from such "buried" pieces. Strategic positioning of propagators that allow you to reach these "internal" pieces is key to winning the game. .PP Since it is relatively easy for one's opponent to prevent one from winning by changing the color of a piece intended to be the 4th propagator in the row of 4, a good strategy is to devise a way to have at least two different pieces that can serve as a 4th piece in the row. Another good strategy is to bury the prospective 4th piece with other non-essential pieces so that the opponent cannot easily reach it, and have multiple propagator paths to it. Then if the opponent blocks one propagator path, another one is available to reach it. .PP It is very useful to anticipate the color of one's subsequent piece, and plan accordingly. For example, if the first player is playing a red, and there are no cyans around, it is useful to place the red next to blue pieces, because they form purple which can be complemented by the green on the next turn. If they are placed next to green pieces, the result is yellow, which cannot be used until 2 turns later. .SH HISTORY The original 2-color version of the game was developed in December 2002. It was based on much simpler rules (basically, each player directly plays his goal piece), but because of the very small initial state space and the proximity of winning states, one player always had the advantage. Several different starting configurations, including randomized starting states, were tried in an attempt to balance the game, but with limited results. .PP Because of these limitations, more elaborate versions of the game were sought. The current 8-color version was first introduced in February 2003. Its main motivation was to postpone winning states until the state space has grown significantly. .PP A min-max algorithm with alpha/beta pruning was introduced to the AI player in April 2003. This replaced the previous, more limited algorithm which only performed well at certain search depths. .PP The "4" in the name "Atom-4" refers obviously to the goal of making the 4-in-a-row. The "atom" part refers to the similarity to atoms forming into a crystal lattice: you can't just stick an atom anywhere in a crystal lattice; it must fit into a "stable" position (in this case, touching two other adjacent "atoms" already on the board). Also, atoms don't just stick together; chemical reactions (color changes) happen when they come together, and some chemical changes have far-reaching effects (color change propagating over whites and blacks). .SH AUTHOR The game concept of Atom-4, the design and implementation of the software version of the game, and the graphics used by the game, were all done by Hwei Sheng Teoh . .SH COPYRIGHT Copyright (C) 2002-2003 by Hwei Sheng Teoh .PP This is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version without ANY WARRANTIES. atom4/COPYRIGHT0100600000175000017500000003542707634007574012763 0ustar hsteohhsteoh GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS atom4/INSTALL0100600000175000017500000000466507634104307012511 0ustar hsteohhsteoh------------------------------------------------------------------------------ ATOM-4 by H. S. Teoh Dec 2002 ------------------------------------------------------------------------------ INSTALLATION INSTRUCTIONS I. REQUIREMENTS Software Requirements: POSIX-compliant system (currently tested under Linux 2.4) Ncurses (other curses-based libraries may work, but they have not been tested) X11R6 (currently tested under XFree86) Build Requirements: Cons 2.2.0 or greater Perl 5.005 or greater (basically, requirement of Cons) GCC 2.95 or greater (currently tested with GCC 3.2. Other C++ compilers may work, but they have not been tested) X11R6 development libraries (including libXpm) prog/lib libraries (should be included in the same archive you got Atom-4 from) II. BUILDING AND INSTALLATION You will probably need to build the prog/lib library first. To do that, simply cd to the root directory of the prob/lib source tree and type cons . To build Atom-4, simply type cons . from the root directory of this source tree, and Cons should take care of the rest. The resulting binary will expect to find its data files in the data/ subdirectory in the source tree, however; for installation purposes you may need to configure the build process as necessary. The Construct script can be configured using the following arguments: BINDIR=$dir Where to install Atom-4 binaries COMPILER=$binary Compiler to use (default: g++) DATADIR=$dir Where data files should be installed MANDIR=$dir Directory to store manpages REALDATADIR=$dir Where Atom-4 should look for data files (default is $DATADIR; this option is mainly for packaging builds where the build-time dir is different from the run-time dir) PROGLIBPATH=$dir Path to prog/lib libraries (default: #../lib) X11LIBPATH=$dir Where to find X11 libraries (default: /usr/X11R6/lib) DEBUG=1 Build with debugging symbols These arguments are passed to Cons during the build. For example, to build and install the Atom-4 binary into /usr/games, and the data files into /usr/share/games/atom4, you would run: cons . BINDIR=/usr/games DATADIR=/usr/share/games/atom4 Currently, the Construct script does not auto-detect X11 libraries and such; you will have to specify this explicitly using X11LIBPATH=... if your X11 libraries are not in /usr/X11R6/lib. atom4/indenter.pl0100700000175000017500000000027707644636720013633 0ustar hsteohhsteoh#!/usr/bin/perl -w # # Indent Atom4 AI logfiles so that it's easier to read! # $indent=0; while (<>) { /\>\>/ && !/PRUNE/ && $indent--; print " "x$indent, $_; /\<\ #include #include // for STDIN_FILENO #include "exception.h" #include "game.h" #include "textui.h" #define NORMAL_COLOR 9 void ncurses_ui::keyhandler::read_ready(eventloop *src, int fd) { ui->handlekey(); // process event ui->refresh(); // update screen with any changes } void ncurses_ui::keyhandler::write_ready(eventloop *src, int fd) {} void ncurses_ui::changehandler::notify_move(atom4 *src, int player, elist &changes) { // First change is always player's actual move boardchange move = *changes.headp(); ui->message("Player %d moves (%d,%d)\n", player, move.x, move.y); notify_clear(src); } void ncurses_ui::changehandler::notify_clear(atom4 *src) { int winner = ui->game->winner(); if (winner!=-1) { if (winner!=STALEMATE) { ui->message("Player %d WINS!!\n", winner); } else { ui->message("STALEMATE: no more moves left\n"); } } ui->refresh(); // update with changes } void ncurses_ui::handlekey() { int pl = game->current_player(); int ch = wgetch(bwin); if (ch==-1) throw exception("Ncurses error: aborting"); // Global keybindings switch (ch) { case 0x0C: redrawwin(stdscr); // force repaint of entire screen return; } // Process key differently depending on game state if (!game->round_over()) { switch (ch) { case KEY_UP: if (last_y>0) last_y--; break; case KEY_DOWN: if (last_yboard_height()-1) last_y++; break; case KEY_LEFT: if (last_x>0) last_x--; break; case KEY_RIGHT: if (last_xboard_width()-1) last_x++; break; case KEY_ENTER: case '\n': case '\r': case ' ': if (!game->is_local_turn()) { message("It's not your turn to move\n"); break; } // Attempt the move if (!game->move(pl, last_x, last_y)) { message("You can't put a marble here"); } else { if (!game->round_over()==-1) { message("Player %d moves (%d,%d)", pl, last_x, last_y); } } break; case 'q': if (ask_yn("Quit current game?")) *exitflag=1; // quit (TBD: should resign player) break; default: message("Unknown key (%d)", ch); } } else { // not in playing mode switch (ch) { case 'n': game->newround(); // start next round last_x=game->board_width()/2; // reset cursor pos last_y=game->board_height()/2; message(""); // clean up stale messages break; case 'q': *exitflag=1; // quit break; default: message("Press 'n' to proceed to the next round, 'q' to quit."); } } } int ncurses_ui::formatstr(char *fmt, ...) { va_list args; int rc; va_start(args,fmt); rc=vsnprintf(strbuf, STRBUF_SIZE, fmt, args); va_end(args); return rc; } int ncurses_ui::message(char *fmt, ...) { va_list args; int rc; va_start(args,fmt); rc=vsnprintf(strbuf, STRBUF_SIZE, fmt, args); mvwaddstr(msgwin, 0, 0, strbuf); wclrtoeol(msgwin); wrefresh(msgwin); va_end(args); return rc; } void ncurses_ui::draw_tile(WINDOW *w, celltype cell) { char *glyph; int color, bold=0; if (cell>='a' && cell<='h') { glyph="()"; color = cellcolors[cell-'a']; bold = cellbold[cell-'a']; } else { // NOTE: this is an ugly hack to force ncurses to update every char // position with the white background; otherwise on some terminals they // use the default blank which may not have the right background. // Basically, we alternate between cell colors 0 and 1 (red); it doesn't // really matter because we're drawing blanks anyway. We take care to // use 0 for the first character so that the cursor doesn't show up with // funny colors on xterm. wcolor_set(w, cellcolors[0], NULL); waddch(w, ' '); color = cellcolors[1]; glyph=" "; } wcolor_set(w, color, NULL); if (bold) wattron(w, A_BOLD); // so that white cells will show waddstr(w, glyph); if (bold) wattroff(w, A_BOLD); // normal for everything else wcolor_set(w, NORMAL_COLOR, NULL); // just need the white background } void render_cell(int x, int y, celltype cell, void *context) { ncurses_ui *iface = (ncurses_ui*)context; wmove(iface->bwin, y, x*2 + (y%2) + 1); iface->draw_tile(iface->bwin, cell); } void ncurses_ui::render_board() { int i, wd; // Draw board game->get_board()->mapcell(render_cell, (void*)this); // Draw board borders wcolor_set(bwin, cellcolors[0], NULL); // just need the white background wd = game->board_width(); for (i=0; iboard_height(); i++) { mvwaddch(bwin, i, 0, '|'); if (i%2) { waddch(bwin, ' '); // so we don't get color gaps in bckgnd mvwaddch(bwin, i, wd*2+2, '|'); } else { mvwaddstr(bwin, i, wd*2+1, " |"); } } wcolor_set(bwin, NORMAL_COLOR, NULL); // just need the white background } void ncurses_ui::render_scorepanel(int player) { int plidx = player-1; int start_y; // ycoor of this player's scoreboard start_y = IOWIN_PLPANEL_Y + plidx*IOWIN_PLPANEL_HEIGHT; if (use_color()) wcolor_set(iowin, colors[plidx], NULL); formatstr("** Player %d **", player); mvwaddstr(iowin, start_y+IOWIN_PANEL_TITLE_Y, 0, strbuf); if (use_color()) wcolor_set(iowin, NORMAL_COLOR, NULL); formatstr("Score: %d", game->score(player)); mvwaddstr(iowin, start_y+IOWIN_PANEL_SCORE_Y, 0, strbuf); } void ncurses_ui::render_iowin() { int i; werase(iowin); wattron(iowin, A_REVERSE); mvwaddstr(iowin, IOWIN_TITLE_Y, 0, " Welcome to Atom-4 " VERSION_STRING " "); wclrtoeol(iowin); wattroff(iowin, A_REVERSE); formatstr("=== Round %d ===", game->current_round()); mvwaddstr(iowin, IOWIN_ROUND_Y, 0, strbuf); for (i=1; i<=NUM_PLAYERS; i++) { render_scorepanel(i); } // Output a prompt for current player // Note: this should come last, so that the cursor is left at the right // place when wait_key() is called. if (!game->round_over()) { mvwaddstr(iowin, IOWIN_PLPANEL_Y + IOWIN_PANEL_PROMPT_Y + (game->current_player()-1)*IOWIN_PLPANEL_HEIGHT, 0, "-- Your turn: "); draw_tile(iowin, game->current_tile()); waddstr(iowin, " --"); } else { if (game->winner() != STALEMATE) { mvwaddstr(iowin, IOWIN_PLPANEL_Y + IOWIN_PANEL_PROMPT_Y + (game->winner()-1)*IOWIN_PLPANEL_HEIGHT, 0, "!!! WINNER !!!"); } } } void ncurses_ui::refresh() { render_board(); render_iowin(); update_panels(); doupdate(); if (game->is_local_turn()) { // update cursor pos wmove(bwin, last_y, last_x*2+(last_y%2)+1); wrefresh(bwin); } } int ncurses_ui::wait_key() { refresh(); // now is a good time to refresh return wgetch(iowin); } int ncurses_ui::ask_yn(char *question) { int ch; message("%s (y/n) ", question); for (;;) { ch=wgetch(msgwin); if (ch=='n' || ch=='N') { waddstr(msgwin, "no"); wrefresh(msgwin); return 0; } if (ch=='y' || ch=='Y') { waddstr(msgwin, "yes"); wrefresh(msgwin); return 1; } } // Never reached return 0; } int ncurses_ui::init_color() { if (start_color()==ERR) return 0; if (has_colors()==FALSE) return 0; if (use_default_colors()==ERR) return 0; // Insufficient color pairs if (COLOR_PAIRS <= 9) return 0; if (init_pair(NORMAL_COLOR, -1, -1)==ERR) return 0; if (init_pair(1, COLOR_BLACK, COLOR_WHITE)==ERR) return 0; if (init_pair(2, COLOR_RED, COLOR_WHITE)==ERR) return 0; if (init_pair(3, COLOR_GREEN, COLOR_WHITE)==ERR) return 0; if (init_pair(4, COLOR_BLUE, COLOR_WHITE)==ERR) return 0; if (init_pair(5, COLOR_YELLOW, COLOR_WHITE)==ERR) return 0; if (init_pair(6, COLOR_CYAN, COLOR_WHITE)==ERR) return 0; if (init_pair(7, COLOR_MAGENTA, COLOR_WHITE)==ERR) return 0; if (init_pair(8, COLOR_WHITE, COLOR_WHITE)==ERR) return 0; // Bold the secondary colors so that they are easily distinguished from // the primary colors (blue and cyan are esp. easy to confuse) cellcolors[0] = 1; cellbold[0] = 0; cellcolors[1] = 2; cellbold[1] = 0; cellcolors[2] = 3; cellbold[2] = 0; cellcolors[3] = 5; cellbold[3] = 1; cellcolors[4] = 4; cellbold[4] = 0; cellcolors[5] = 7; cellbold[5] = 1; cellcolors[6] = 6; cellbold[6] = 1; cellcolors[7] = 8; cellbold[7] = 1; // Player colors: maybe we should just revert to normal color? colors[0] = NORMAL_COLOR; colors[1] = NORMAL_COLOR; return 1; } ncurses_ui::ncurses_ui(atom4 *gm, eventloop *eloop, int *indicator, int opts) : interface(gm), options(opts), loop(eloop), exitflag(indicator), khandler(this), notifier(this) { int bwd, bht; int i; /* Init ncurses */ initscr(); /* Color support -- now it's mandatory since we can't easily represent * 8 colors in monochrome! */ if (!init_color()) { throw exception("Cannot initialize color subsystem\n"); // options &= ~ENABLE_COLOR; // color initialization failed } /* General options */ cbreak(); noecho(); nonl(); /* Setup ncurses window for board */ bwd = game->board_width()*2+3; bht = game->board_height(); bwin = newwin(bht, bwd, BOARDWIN_Y, BOARDWIN_X); bpanel = new_panel(bwin); scrollok(bwin, FALSE); keypad(bwin, TRUE); /* Put selection cursor at roughly the center of the board */ last_x = game->board_width()/2; last_y = game->board_height()/2; /* Setup ncurses window for displaying player stats */ iowin = newwin(LINES-IOWIN_Y, COLS-bwd-1, IOWIN_Y, BOARDWIN_X+bwd+1); iopanel = new_panel(iowin); scrollok(bwin, FALSE); keypad(iowin, TRUE); // else ESC's get lost when we switch // between panes /* Setup general message panel */ msgwin = newwin(1, COLS, BOARDWIN_Y + bht + 1, 0); msgpanel = new_panel(msgwin); scrollok(msgwin, FALSE); keypad(msgwin, TRUE); // else ESC's get lost when we switch // between panes /* Initial screen refresh */ refresh(); /* Setup async callbacks */ loop->register_handler(eventloop::READER, STDIN_FILENO, &khandler); game->add_notifier(¬ifier); } ncurses_ui::~ncurses_ui() { /* Get rid of message panel */ del_panel(msgpanel); delwin(msgwin); /* Get rid of scoreboard panel */ del_panel(iopanel); delwin(iowin); /* Get rid of board panel */ del_panel(bpanel); delwin(bwin); /* Exit ncurses mode */ endwin(); } atom4/ncurses/textui.h0100600000175000017500000000466407644516414014643 0ustar hsteohhsteoh/* * Atom-4 ncurses interface * Header file * * $Id: textui.h,v 1.14 2003/04/08 09:57:15 hsteoh Exp hsteoh $ */ #include #include #include #include "event.h" #include "game.h" #include "interface.h" #include "triboard.h" #define BOARDWIN_X 0 #define BOARDWIN_Y 1 #define IOWIN_Y 0 #define IOWIN_TITLE_Y 0 #define IOWIN_ROUND_Y 2 #define IOWIN_PLPANEL_Y 4 #define IOWIN_PLPANEL_HEIGHT 6 #define IOWIN_PANEL_TITLE_Y 0 #define IOWIN_PANEL_SCORE_Y 1 #define IOWIN_PANEL_PROMPT_Y 3 #define IOWIN_WAITKEY_X 0 #define IOWIN_WAITKEY_Y 3 #define STRBUF_SIZE 512 #define NUM_CELLTYPES 8 // 8 colors class ncurses_ui : public interface { public: enum opt_t { // these can be OR'd together ENABLE_COLOR=1 }; private: int options; // (technically, OR-ified opt_t) eventloop *loop; // [R] int *exitflag; // [R] class keyhandler : public eventhandler { ncurses_ui *ui; // [R] public: keyhandler(ncurses_ui *_ui) : ui(_ui) {} virtual void read_ready(eventloop *src, int fd); virtual void write_ready(eventloop *src, int fd); } khandler; class changehandler : public atom4notifier { ncurses_ui *ui; // [R] public: changehandler(ncurses_ui *_ui) : ui(_ui) {} virtual void notify_move(atom4 *src, int player, elist &changes); virtual void notify_clear(atom4 *src); } notifier; void handlekey(); // Game board WINDOW *bwin; PANEL *bpanel; triboard *board; // [R] int last_x, last_y; // last cursor position on board short cellcolors[NUM_CELLTYPES]; // ncurses color pairs char cellbold[NUM_CELLTYPES]; // color pairs which must be bolded // I/O panel WINDOW *iowin; PANEL *iopanel; // Players info int colors[NUM_PLAYERS]; // cosmetic settings :-) // Message panel WINDOW *msgwin; PANEL *msgpanel; // Miscellaneous stuff char strbuf[STRBUF_SIZE]; // temp buffer for formatting strings int use_color() { return options&ENABLE_COLOR; } int formatstr(char *fmt, ...); int message(char *fmt, ...); void draw_tile(WINDOW *w, celltype cell); friend void render_cell(int x, int y, celltype cell, void *context); void render_board(); void render_scorepanel(int player); void render_iowin(); void refresh(); int wait_key(); int ask_yn(char *msg); int init_color(); public: ncurses_ui(atom4 *game, eventloop *loop, int *exitflag, int options=0); ~ncurses_ui(); }; atom4/ncurses/Conscript0100600000175000017500000000062507633765754015043 0ustar hsteohhsteoh#!/usr/bin/cons # # Atom-4 ncurses module build script. # # $Id: Conscript,v 1.3 2003/03/13 02:24:19 hsteoh Exp hsteoh $ # Import qw(COMPILER CFLAGS BINDIR INCDIR OBJDIR PROGLIBPATH NCURSESLIB); $env = new cons( CC => $COMPILER, CFLAGS => $CFLAGS, CPPPATH => "$INCDIR:$PROGLIBPATH/include", ); Install $env $INCDIR, 'textui.h'; Install $env $OBJDIR, 'textui.o'; Objects $env 'textui.cc'; atom4/x/redball.xpm0100600000175000017500000000245607604745572014072 0ustar hsteohhsteoh/* XPM */ static char * redball_xpm[] = { "32 32 8 1 16 16", " c None", ". c dark red", "X c #e05050", "o c #f0a0a0", "O c #E02020", "+ c white", "@ c red", "# c #e00000", " ........ ", " .............. ", " ...Xo............. ", " ..Oo+o.............. ", " ..Oo++o................. ", " .Xo++o.................. ", " .Xo++X.................... ", " ..o++X...................... ", " .X++X....................... ", " ..++o......................... ", " .X+o.......................... ", " .o+O.......................... ", "..+O............................", "..o.............................", "..O.............................", "................................", "................................", "................................", ".............................@..", "............................#@..", " ...........................@@. ", " ..........................#@@. ", " ..........................#@.. ", " ........................#@@. ", " .......................#@@.. ", " .....................#@@@. ", " ...................#@@@. ", " .................##@@@.. ", " ...........#####@@.. ", " ...@@########@@@.. ", " ..@@@@@@@@@@.. ", " ........ "}; atom4/x/greenball.xpm0100600000175000017500000000246407604745614014414 0ustar hsteohhsteoh/* XPM */ static char * greenball_xpm[] = { "32 32 8 1 16 16", " c None", ". c dark green", "X c #50e050", "o c #a0f0a0", "O c #20E020", "+ c white", "@ c green", "# c #00e000", " ........ ", " .............. ", " ...Xo............. ", " ..Oo+o.............. ", " ..Oo++o................. ", " .Xo++o.................. ", " .Xo++X.................... ", " ..o++X...................... ", " .X++X....................... ", " ..++o......................... ", " .X+o.......................... ", " .o+O.......................... ", "..+O............................", "..o.............................", "..O.............................", "................................", "................................", "................................", ".............................@..", "............................#@..", " ...........................@@. ", " ..........................#@@. ", " ..........................#@.. ", " ........................#@@. ", " .......................#@@.. ", " .....................#@@@. ", " ...................#@@@. ", " .................##@@@.. ", " ...........#####@@.. ", " ...@@########@@@.. ", " ..@@@@@@@@@@.. ", " ........ "}; atom4/x/blueball.xpm0100600000175000017500000000246107604745644014243 0ustar hsteohhsteoh/* XPM */ static char * blueball_xpm[] = { "32 32 8 1 16 16", " c None", ". c dark blue", "X c #5050e0", "o c #a0a0f0", "O c #2020e0", "+ c white", "@ c blue", "# c #0000e0", " ........ ", " .............. ", " ...Xo............. ", " ..Oo+o.............. ", " ..Oo++o................. ", " .Xo++o.................. ", " .Xo++X.................... ", " ..o++X...................... ", " .X++X....................... ", " ..++o......................... ", " .X+o.......................... ", " .o+O.......................... ", "..+O............................", "..o.............................", "..O.............................", "................................", "................................", "................................", ".............................@..", "............................#@..", " ...........................@@. ", " ..........................#@@. ", " ..........................#@.. ", " ........................#@@. ", " .......................#@@.. ", " .....................#@@@. ", " ...................#@@@. ", " .................##@@@.. ", " ...........#####@@.. ", " ...@@########@@@.. ", " ..@@@@@@@@@@.. ", " ........ "}; atom4/x/tritile16.xpm0100600000175000017500000000415507604737165014305 0ustar hsteohhsteoh/* XPM */ static char * tritile16_xpm[] = { "32 56 6 1", " c black", ". c white", "X c grey", "o c #d0d0d0", "O c #404040", "+ c #e0e0e0", " .............................. ", "X oXXXXXXXXXXXXXXXXXXXXXXXXXXXO.", "X oXXXXXXXXXXXXXXXXXXXXXXXXXXX .", "XX oXXXXXXXXXXXXXXXXXXXXXXXXX .X", "XX oXXXXXXXXXXXXXXXXXXXXXXXXX .X", "XXX oXXXXXXXXXXXXXXXXXXXXXXX .+X", "XXXOoXXXXXXXXXXXXXXXXXXXXXXX .XX", "XXXX oXXXXXXXXXXXXXXXXXXXXX .XXX", "XXXXX oXXXXXXXXXXXXXXXXXXXO.+XXX", "XXXXX oXXXXXXXXXXXXXXXXXXX .XXXX", "XXXXXX oXXXXXXXXXXXXXXXXX .XXXXX", "XXXXXX oXXXXXXXXXXXXXXXXX .XXXXX", "XXXXXXX oXXXXXXXXXXXXXXX .+XXXXX", "XXXXXXXOoXXXXXXXXXXXXXXX .XXXXXX", "XXXXXXXX oXXXXXXXXXXXXX .XXXXXXX", "XXXXXXXXX oXXXXXXXXXXXO.+XXXXXXX", "XXXXXXXXX oXXXXXXXXXXX .XXXXXXXX", "XXXXXXXXXX oXXXXXXXXX .XXXXXXXXX", "XXXXXXXXXX oXXXXXXXXX .XXXXXXXXX", "XXXXXXXXXXX oXXXXXXX .+XXXXXXXXX", "XXXXXXXXXXXOoXXXXXXX .XXXXXXXXXX", "XXXXXXXXXXXX oXXXXX .XXXXXXXXXXX", "XXXXXXXXXXXXX oXXXO.+XXXXXXXXXXX", "XXXXXXXXXXXXX oXXX .XXXXXXXXXXXX", "XXXXXXXXXXXXXX oX .XXXXXXXXXXXXX", "XXXXXXXXXXXXXX oX .XXXXXXXXXXXXX", "XXXXXXXXXXXXXXXOO.+XXXXXXXXXXXXX", " OO ", "...............OO...............", "XXXXXXXXXXXXXX .X oXXXXXXXXXXXXX", "XXXXXXXXXXXXXX .X oXXXXXXXXXXXXX", "XXXXXXXXXXXXX .+XX oXXXXXXXXXXXX", "XXXXXXXXXXXXX .XXXOoXXXXXXXXXXXX", "XXXXXXXXXXXX .XXXXX oXXXXXXXXXXX", "XXXXXXXXXXXO.+XXXXXX oXXXXXXXXXX", "XXXXXXXXXXX .XXXXXXX oXXXXXXXXXX", "XXXXXXXXXX .XXXXXXXXX oXXXXXXXXX", "XXXXXXXXXX .XXXXXXXXX oXXXXXXXXX", "XXXXXXXXX .+XXXXXXXXXX oXXXXXXXX", "XXXXXXXXX .XXXXXXXXXXXOoXXXXXXXX", "XXXXXXXX .XXXXXXXXXXXXX oXXXXXXX", "XXXXXXXO.+XXXXXXXXXXXXXX oXXXXXX", "XXXXXXX .XXXXXXXXXXXXXXX oXXXXXX", "XXXXXX .XXXXXXXXXXXXXXXXX oXXXXX", "XXXXXX .XXXXXXXXXXXXXXXXX oXXXXX", "XXXXX .+XXXXXXXXXXXXXXXXXX oXXXX", "XXXXX .XXXXXXXXXXXXXXXXXXXOoXXXX", "XXXX .XXXXXXXXXXXXXXXXXXXXX oXXX", "XXXO.+XXXXXXXXXXXXXXXXXXXXXX oXX", "XXX .XXXXXXXXXXXXXXXXXXXXXXX oXX", "XX .XXXXXXXXXXXXXXXXXXXXXXXXX oX", "XX .XXXXXXXXXXXXXXXXXXXXXXXXX oX", "X .+XXXXXXXXXXXXXXXXXXXXXXXXXX o", "X .XXXXXXXXXXXXXXXXXXXXXXXXXXXOo", " .XXXXXXXXXXXXXXXXXXXXXXXXXXXXX ", " "}; atom4/x/xutil.h0100600000175000017500000001127207623327313013237 0ustar hsteohhsteoh/* * Simple X11 interfacing routines * Header file * * $Id: xutil.h,v 1.12 2003/02/15 03:06:25 hsteoh Exp hsteoh $ */ #ifndef XUTIL_H #define XUTIL_H #include #include #include // NOTE: *MUST* be prog/lib version!!! #include "event.h" /* Note: fatal errors will cause exceptions to be thrown. */ class xwindow; // forward decl class xconnection { Display *disp; int scrn; // screen number // Event loop integration eventloop *loop; // [R] class eventcallback : public eventhandler { friend class xconnection; xconnection *conn; // [R] eventcallback(xconnection *connection) : conn(connection) {} ~eventcallback() {} void read_ready(eventloop *src, int fd); void write_ready(eventloop *src, int fd); } event_cb; // Color-handling stuff int depth; // screen depth Colormap cmap; // application-wide colormap // Event-dispatching stuff elist winlist; // list of registered windows void dispatch_event(XEvent ev); // handle single event void process_pending_event(); // wait for next event & process it public: xconnection(char *xserver, eventloop *loop); // ([R],[R]) ~xconnection(); // Access functions Display *display() { return disp; } int screen() { return scrn; } int scrn_depth() { return depth; } int server_socket() { return ConnectionNumber(disp); } // Convenience functions unsigned long black() { return BlackPixel(disp, scrn); } unsigned long white() { return WhitePixel(disp, scrn); } // Event-handling void register_window(xwindow *win); void unregister_window(xwindow *win); }; /* Generic xwindow base class (mainly for registering with event loop) */ class xwindow { friend class xconnection; void update_attrs(); // update window attributes (wd, ht, // etc.) protected: xconnection *conn; Window win; int wd, ht; // current (internal) width/height int border_wd; // current size of border public: // Note: derived classes should call XSelectInput themselves; this base class // assumes nothing about what events are interesting xwindow(xconnection *conn, xwindow *parent, unsigned int x, unsigned int y, unsigned int width, unsigned int height, unsigned int border_width, unsigned long border_color, unsigned long bckgnd_color); virtual ~xwindow(); // Access functions (these return actual dimensions in pixels) int width() { return wd; } int height() { return ht; } int ext_width() { return wd+border_wd*2; } // external width of window int ext_height() { return ht+border_wd*2; } // external height int border_width() { return border_wd; } // Generic hook to force windows to repaint themselves virtual void refresh(); // This one is for the convenience of parent window dispatchers int operator== (Window w) { return win==w; } // Event dispatcher for window. Subclasses generally won't need to override // this, since they can just override the individual event handlers. This // method will invoke the individual event handlers based on the event type. // Note that subclasses still have to call XSelectInput() if they want to // actually receive any of these events. (This class does not call // XSelectInput() automatically.) virtual void handle_event(XEvent ev); // By default, these do nothing. Subclasses should override these to do // something sensible. virtual void expose(XExposeEvent ev); virtual void focus_in(XFocusInEvent ev); virtual void focus_out(XFocusOutEvent ev); virtual void key_press(XKeyPressedEvent ev); virtual void key_release(XKeyReleasedEvent ev); virtual void mouse_buttondown(XButtonPressedEvent ev); virtual void mouse_buttonup(XButtonReleasedEvent ev); virtual void mouse_enter(XEnterWindowEvent ev); virtual void mouse_leave(XLeaveWindowEvent ev); virtual void mouse_move(XMotionEvent ev); }; /* Simple window class designed to be used by single-window applications. * A lot of stuff should give reasonable simple defaults; the application * itself should subclass this class to provide app-specific functions. */ class appwindow : public xwindow { protected: Display *disp; // [R] (for convenience only) public: appwindow(xconnection *conn, char *win_name, char *icon_name, int width, int height, char *resource_class, char *resource_name); ~appwindow(); }; /* Tiled background handling stuff */ class tiled_bckgnd { xconnection *conn; // [R] Drawable d; // drawable to draw on Pixmap tile; GC tilegc; public: // FIXME: should use generic image loader tiled_bckgnd(xconnection *conn, Drawable d, char *xpmfile); ~tiled_bckgnd(); void paint(int x, int y, int w, int h); }; #endif // XUTIL_H atom4/x/xutil.cc0100600000175000017500000001721607633766025013411 0ustar hsteohhsteoh/* * Simple X11 interfacing routines * Implementation file * * $Id: xutil.cc,v 1.13 2003/03/13 02:25:17 hsteoh Exp hsteoh $ */ #include #include "exception.h" #include "xutil.h" /* * * Class xconnection * */ void xconnection::eventcallback::read_ready(eventloop *src, int fd) { // Make sure no pending events from the X server are queued; otherwise // when we return, we might block before completing all necessary updates while (XPending(conn->display())) { conn->process_pending_event(); XFlush(conn->display()); } } void xconnection::eventcallback::write_ready(eventloop *src, int fd) {} void xconnection::dispatch_event(XEvent ev) { elistiter it; if (ev.type==KeymapNotify) { // TBD... } else { // Not a keymap event; forward it to the appropriate window for (it=winlist.headp(); it; it++) { if ((*it)->win == ev.xany.window) { (*it)->handle_event(ev); return; // done } } // No window found: ignore for now (ideally, we should trigger a warning // because this means a window forgot to register with us) } } void xconnection::process_pending_event() { XEvent ev; XNextEvent(disp, &ev); dispatch_event(ev); } xconnection::xconnection(char *xserver, eventloop *eloop) : loop(eloop), event_cb(this) { disp = XOpenDisplay(xserver); if (!disp) throw exception("@Unable to open X display %s", xserver ? xserver : "(default display)"); scrn=XDefaultScreen(disp); depth=DefaultDepth(disp, scrn); cmap=DefaultColormap(disp, scrn); // Register with event handler loop->register_handler(eventloop::READER, ConnectionNumber(disp), &event_cb); } xconnection::~xconnection() { loop->unregister_handler(eventloop::READER, ConnectionNumber(disp)); XCloseDisplay(disp); } void xconnection::register_window(xwindow *win) { winlist.append(win); } void xconnection::unregister_window(xwindow *win) { elistiter it, prev; prev.invalidate(); for (it=winlist.headp(); it; it++) { if ((*it) == win) { winlist.remove(prev); } prev=it; } } /* * * Class xwindow * */ void xwindow::update_attrs() { XWindowAttributes attr; if (!XGetWindowAttributes(conn->display(), win, &attr)) throw exception("Error while updating window attributes\n"); wd = attr.width; ht = attr.height; border_wd = attr.border_width; } xwindow::xwindow(xconnection *connection, xwindow *parent, unsigned int x, unsigned int y, unsigned int width, unsigned int height, unsigned int border_width, unsigned long border_color, unsigned long bckgnd_color) { Window parentwin; conn = connection; wd = width; ht = height; if (parent) { parentwin = parent->win; } else { parentwin = RootWindow(conn->display(), conn->screen()); } win=XCreateSimpleWindow(conn->display(), parentwin, x,y, width, height, border_width, border_color, bckgnd_color); // Register with event dispatcher so that we will receive X events conn->register_window(this); update_attrs(); // freshen window attributes } xwindow::~xwindow() { XDestroyWindow(conn->display(), win); } void xwindow::refresh() {} void xwindow::handle_event(XEvent ev) { if (ev.xany.window != win) return; // safeguard switch (ev.type) { case Expose: expose(ev.xexpose); break; case FocusIn: focus_in(ev.xfocus); break; case FocusOut: focus_out(ev.xfocus); break; case KeyPress: key_press(ev.xkey); break; case KeyRelease: key_release(ev.xkey); break; case ButtonPress: mouse_buttondown(ev.xbutton); break; case ButtonRelease: mouse_buttonup(ev.xbutton); break; case EnterNotify: mouse_enter(ev.xcrossing); break; case LeaveNotify: mouse_leave(ev.xcrossing); break; case MotionNotify: mouse_move(ev.xmotion); break; default: // For now, ignore unknown events -- ideally, this should call a virtual // function that can be extended by a derived class break; } } void xwindow::expose(XExposeEvent ev) {} void xwindow::focus_in(XFocusInEvent ev) {} void xwindow::focus_out(XFocusOutEvent ev) {} void xwindow::key_press(XKeyPressedEvent ev) {} void xwindow::key_release(XKeyReleasedEvent ev) {} void xwindow::mouse_buttondown(XButtonPressedEvent ev) {} void xwindow::mouse_buttonup(XButtonReleasedEvent ev) {} void xwindow::mouse_enter(XEnterWindowEvent ev) {} void xwindow::mouse_leave(XLeaveWindowEvent ev) {} void xwindow::mouse_move(XMotionEvent ev) {} /* * * Class appwindow * */ appwindow::appwindow(xconnection *connection, char *winstr, char *iconstr, int win_wd, int win_ht, char *resource_class, char *resource_name) : xwindow(connection, NULL, 0,0, win_wd, win_ht, 0,0,0), disp(connection->display()) { XWMHints *wmhints; XClassHint *classhints; XSizeHints *sizehints; XTextProperty win_name, icon_name; // Setup window manager hints wmhints = XAllocWMHints(); if (!wmhints) throw exception("Cannot allocate WM hints"); wmhints->initial_state = NormalState; wmhints->input = True; wmhints->flags |= StateHint | InputHint; wmhints->icon_pixmap = 0; /* wmhints->flags = IconPixmapHint; */ /* NOTE: if we're gonna set icon_pixmap to NULL, we should NOT be setting * IconPixmapHint!!! That was why XScavenger was showing that oogly messed * up "icon" (or non-icon :-P) */ classhints = XAllocClassHint(); if (!classhints) throw exception("Cannot allocate class hints"); classhints->res_name = resource_class; classhints->res_class = resource_name; sizehints = XAllocSizeHints(); if (!sizehints) throw exception("Cannot allocate size hints"); sizehints->flags = PSize | PMinSize | PMaxSize; sizehints->min_width = sizehints->max_width = wd; sizehints->min_height = sizehints->max_height = ht; if (!XStringListToTextProperty(&winstr, 1, &win_name)) throw exception("Error while creating TextProperty"); if (!XStringListToTextProperty(&iconstr, 1, &icon_name)) throw exception("Error while creating TextProperty"); // Set these as our WM settings // FIXME: // - a lot of stuff here needs to be generalized. But I can't be bothered // to do that yet. XSetWMProperties(disp, win, &win_name, &icon_name, NULL, 0, sizehints, wmhints, classhints); // Cleanup XFree((void *)wmhints); XFree((void *)classhints); XFree((void *)sizehints); XFree((void *)win_name.value); XFree((void *)icon_name.value); // Map window XMapWindow(disp, win); } appwindow::~appwindow() {} /* * * Class tiled_bckgnd * */ // FIXME: should use generic image loader tiled_bckgnd::tiled_bckgnd(xconnection *connection, Drawable drawable, char *xpmfile) : conn(connection), d(drawable) { Display *disp = conn->display(); XpmAttributes attr; XGCValues gcvals; // XPM creation attributes attr.colormap = DefaultColormap(disp, conn->screen()); attr.closeness = 32768; // FIXME: should be in graphics manager attr.valuemask = XpmColormap | XpmCloseness; // (Note: NULL is passed for shapemask since we don't care about that) if (XpmReadFileToPixmap(disp, d, xpmfile, &tile, NULL, &attr) != XpmSuccess) { throw exception("@Unable to load tile pixmap from: %s", xpmfile); } // GC creation attributes gcvals.tile = tile; gcvals.fill_style = FillTiled; tilegc = XCreateGC(disp, d, GCFillStyle | GCTile, &gcvals); } tiled_bckgnd::~tiled_bckgnd() { Display *disp=conn->display(); XFreePixmap(disp, tile); XFreeGC(disp, tilegc); } void tiled_bckgnd::paint(int x, int y, int w, int h) { XFillRectangle(conn->display(), d, tilegc, x,y, w,h); } atom4/x/xsprite.h0100600000175000017500000000526407633524450013576 0ustar hsteohhsteoh/* * Simple X11 sprite handling * Header file * * $Id: xsprite.h,v 1.3 2003/02/09 03:51:05 hsteoh Exp hsteoh $ */ #ifndef XSPRITE_H #define XSPRITE_H #include "xutil.h" // Sprite engine for giving context to sprites. There should be one sprite // engine per application window, theoretically. // The reason we have this is so that we can minimize X server resource usage // by putting common stuff, such as GC's, here, instead of one copy per // sprite. class xsprite_engine { xconnection *conn; // [R] Drawable d; // Drawable to render sprites on int closeness; // XPM color closeness range (TEMP) GC copygc; GC maskgc; GC drawgc; public: xsprite_engine(xconnection *conn, Drawable d, int color_closeness); ~xsprite_engine(); // Convenience functions Display *display() { return conn->display(); } int screen() { return conn->screen(); } int scrn_depth() { return conn->scrn_depth(); } unsigned long black() { return conn->black(); } unsigned long white() { return conn->white(); } Drawable get_drawable() { return d; } int get_closeness() { return closeness; } GC get_copygc() { return copygc; } GC get_maskgc() { return maskgc; } GC get_drawgc() { return drawgc; } // Possible future extension: a file loader that can understand all kinds // of graphic formats. Maybe a wrapper to Imlib or similar. //XImage *load_imagefile(char *filename); }; // For now, we provide a simple interface to create sprites from XPM files class xflatsprite { xsprite_engine *eng; // [R] sprite engine context Pixmap image; Pixmap mask; int wd, ht; // width & height int ox, oy; // image origin (==XPM hotspot) public: xflatsprite(xsprite_engine *engine, char *xpmfile); ~xflatsprite(); void draw(Drawable d, int x, int y); int width() { return wd; } int height() { return ht; } // Location of hotspot int org_x() { return ox; } int org_y() { return oy; } }; // Buffer for saving backgrounds, for animating sprites. class xsavebuf { xsprite_engine *eng; // [R] int wmax, hmax; // maximum dimensions Pixmap bckgnd; Drawable bckgnd_d; // Drawable to restore to int last_x, last_y; int last_wd, last_ht; int saved; // does bckgnd contain valid data public: xsavebuf(xsprite_engine *engine, Drawable d, int max_width, int max_height); ~xsavebuf(); void save(Drawable d, int x, int y, int wd, int ht); void restore(); int max_width() { return wmax; } int max_height() { return hmax; } }; // FIXME: need to generalize this class xsprite { public: xsprite(xsprite_engine *engine, xflatsprite *spr, char *xpmfile); ~xsprite(); void sdraw(Drawable d, int x, int y); void erase(); }; #endif // XSPRITE_H atom4/x/xsprite.cc0100600000175000017500000001035207633766026013735 0ustar hsteohhsteoh/* * Simple X11 sprite handling * Implementation file * * $Id: xsprite.cc,v 1.5 2003/03/13 02:25:17 hsteoh Exp hsteoh $ */ #include #include "exception.h" #include "xsprite.h" xsprite_engine::xsprite_engine(xconnection *connection, Drawable drawable, int color_closeness) : conn(connection) { Display *disp = conn->display(); XGCValues gcvals; // Init d = drawable; closeness = color_closeness; // Create sprite GC's gcvals.function = GXcopy; copygc = XCreateGC(disp, d, GCFunction, &gcvals); gcvals.function = GXand; maskgc = XCreateGC(disp, d, GCFunction, &gcvals); gcvals.function = GXor; drawgc = XCreateGC(disp, d, GCFunction, &gcvals); } xsprite_engine::~xsprite_engine() { Display *disp = conn->display(); XFreeGC(disp, copygc); XFreeGC(disp, maskgc); XFreeGC(disp, drawgc); } xflatsprite::xflatsprite(xsprite_engine *engine, char *xpmfile) : eng(engine) { Display *disp = eng->display(); Drawable d = eng->get_drawable(); XpmAttributes attr; XImage *img, *msk; // raw image & mask int depth = eng->scrn_depth(); // Init eng = engine; // XPM creation attributes attr.colormap = DefaultColormap(disp, eng->screen()); attr.closeness = eng->get_closeness(); attr.valuemask = XpmColormap | XpmCloseness; // (the following is to catch the case when no hotspot is set in the file) attr.x_hotspot = attr.y_hotspot = 0; if (XpmReadFileToImage(disp, xpmfile, &img, &msk, &attr) != XpmSuccess) { throw exception("@Error while loading pixmap file: %s", xpmfile); } // Store XPM info returned by libXpm wd = attr.width; ht = attr.height; ox = attr.x_hotspot; oy = attr.y_hotspot; // Upload sprite image to server image = XCreatePixmap(disp, d, wd, ht, depth); XPutImage(disp, image, eng->get_copygc(), img, 0,0, 0,0, wd,ht); // Create sprite mask with the correct color depth for this display. // Notes: // - The XPM shape mask is depth 1, which usually isn't what we want; so // we'll need to convert it pixel by pixel into something we can AND with. // - here, we reuse *img since it is conveniently of the right dimensions // and depth already. No need to manually create another XImage. for (int y=0; yblack() : eng->white()); } } // Upload converted mask to server mask = XCreatePixmap(disp, d, wd, ht, depth); XPutImage(disp, mask, eng->get_copygc(), img, 0,0, 0,0, wd,ht); // Cleanup XDestroyImage(img); XDestroyImage(msk); } xflatsprite::~xflatsprite() { Display *disp = eng->display(); XFreePixmap(disp, image); XFreePixmap(disp, mask); } void xflatsprite::draw(Drawable d, int x, int y) { Display *disp = eng->display(); // Mask out area first XCopyArea(disp, mask, d, eng->get_maskgc(), 0,0, wd,ht, x-ox,y-oy); // OR image into hole XCopyArea(disp, image, d, eng->get_drawgc(), 0,0, wd,ht, x-ox,y-oy); } xsavebuf::xsavebuf(xsprite_engine *engine, Drawable d, int max_width, int max_height) : eng(engine), wmax(max_width), hmax(max_height) { // Storage area for background bckgnd = XCreatePixmap(eng->display(), eng->get_drawable(), wmax, hmax, eng->scrn_depth()); last_x = last_y = 0; last_wd = last_ht = 0; saved = 0; } xsavebuf::~xsavebuf() { XFreePixmap(eng->display(), bckgnd); } void xsavebuf::save(Drawable d, int x, int y, int wd, int ht) { // Sanity guards if (wd<0 || ht<0) { saved=0; // invalidate saved background return; // nothing more to do } if (wd>wmax || ht>hmax) throw exception("xsavebuf: requested save area exceeds buffer capacity"); XCopyArea(eng->display(), d, bckgnd, eng->get_copygc(), x,y, wd,ht, 0,0); bckgnd_d = d; last_x = x; last_y = y; last_wd = wd; last_ht = ht; saved = 1; } void xsavebuf::restore() { if (saved) { XCopyArea(eng->display(), bckgnd, bckgnd_d, eng->get_copygc(), 0,0, last_wd, last_ht, last_x, last_y); saved = 0; } } xsprite::xsprite(xsprite_engine *engine, xflatsprite *sprite, char *xpmfile) { } xsprite::~xsprite() { } void xsprite::sdraw(Drawable d, int x, int y) { } void xsprite::erase() { } atom4/x/xatom4.cc0100600000175000017500000000726107644516441013454 0ustar hsteohhsteoh/* * X11 client for Atom-4 * Implementation file * * $Id: xatom4.cc,v 1.29 2003/04/08 09:57:43 hsteoh Exp hsteoh $ */ #include // FOR NOW ONLY #include #include #include #include #include "exception.h" #include "xatom4.h" #define WIN_WIDTH 640 #define WIN_HEIGHT 480 #define BOARD_X 0 // location of board pane #define BOARD_Y 0 #define XCELLS 16 // board dimensions #define YCELLS 16 #define COLOR_CLOSENESS 32768 /* * * Class XAtom4 * */ void XAtom4::gamestate_notifier::notify_move(atom4 *src, int player, elist &chg) { app->refresh(); // TBD: print message about player's move XFlush(app->conn->display()); // update display now } void XAtom4::gamestate_notifier::notify_clear(atom4 *src) { app->refresh(); XFlush(app->conn->display()); // update display now } XAtom4::XAtom4(xconnection *xconn, atom4 *gm, int *indicator) : appwindow(xconn, "Atom4 X11 Client", "XAtom4", WIN_WIDTH, WIN_HEIGHT, "XAtom4", "XAtom4"), conn(xconn), game(gm), exitflag(indicator), notifier(this) { Display *disp = conn->display(); unsigned int score_x; eng = new xsprite_engine(conn, win, COLOR_CLOSENESS); if (!eng) throw exception("Unable to initialize sprite engine"); boardpane = new xtriboard(conn, eng, this, game, BOARD_X, BOARD_Y); score_x = BOARD_X+boardpane->ext_width(); scorepane = new xscoreboard(conn, eng, this, game, score_x, 0, WIN_WIDTH-score_x, boardpane->height()); // Select events we're interested in XSelectInput(disp, win, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask | FocusChangeMask); // Map sub-windows XMapSubwindows(disp, win); *exitflag=0; // Register game state watcher game->add_notifier(¬ifier); } XAtom4::~XAtom4() { game->remove_notifier(¬ifier); delete scorepane; delete boardpane; } void XAtom4::refresh() { boardpane->refresh(); scorepane->refresh(); } void XAtom4::expose(XExposeEvent ev) { int x=ev.x, y=ev.y, w=ev.width, h=ev.height; // nothing for now } void XAtom4::key_press(XKeyPressedEvent ev) { KeySym keysym = XLookupKeysym(&ev, 0); switch(keysym) { case XK_k: case XK_K: game->reset(); break; case XK_n: case XK_N: if (game->round_over()) { game->newround(); } break; case XK_q: case XK_Q: *exitflag=1; // signal event loop to quit break; } } void XAtom4::mouse_buttondown(XButtonPressedEvent ev) { int x=ev.x, y=ev.y; int bx,by; // board coors if (*boardpane==ev.subwindow) { // Event is on board window if (!game->is_local_turn()) return; // not playing, nothing to do if (ev.button==1) { boardpane->calc_bcoor(x,y,bx,by); if (game->check_legal(bx,by)) { boardpane->cursor_off(); game->move(game->current_player(), bx,by); // Update cursor if there's still a next turn if (!game->round_over()) { boardpane->set_cursor(game->current_tile()); } } else { // TODO: print error message } } // endif(ev.button) } else { // For now, ignore button events anywhere else } } /* * * Class x11ui * */ x11ui::x11ui(char *xserver, atom4 *game, eventloop *loop, int *exitflag) : interface(game), conn(xserver,loop), game_app(&conn, game, exitflag) { // Make sure that all initialization X requests are sent out before we // enter event loop; else we might end up in a deadlock. XFlush(conn.display()); } x11ui::~x11ui() {} atom4/x/xatom4.h0100600000175000017500000000304207644516441013307 0ustar hsteohhsteoh/* * X11 client for Atom-4 * Header file * * $Id: xatom4.h,v 1.16 2003/04/08 09:57:43 hsteoh Exp hsteoh $ */ #ifndef XATOM4_H #define XATOM4_H #include "event.h" #include "game.h" #include "interface.h" #include "xsprite.h" #include "xscoreboard.h" #include "xtriboard.h" #include "xutil.h" class XAtom4 : public appwindow { xconnection *conn; // [R] connection to X server atom4 *game; // [R] game engine xsprite_engine *eng; // [O] xtriboard *boardpane; // [O] game board xscoreboard *scorepane; // [O] score panes int *exitflag; // exit indicator checked by event // loop // Multiplayer support class gamestate_notifier : public atom4notifier { XAtom4 *app; // [R] public: gamestate_notifier(XAtom4 *appwin) : app(appwin) {} void notify_move(atom4 *src, int player, elist &chg); void notify_clear(atom4 *src); } notifier; public: // *exitflag should point to a variable which this class will use to // indicate to the event loop that it should exit. Of course, whether this // is actually what is monitored by the event loop is main()'s decision. XAtom4(xconnection *conn, atom4 *game, int *exitflag); ~XAtom4(); // Maintenance void refresh(); // Event handlers void expose(XExposeEvent ev); void key_press(XKeyPressedEvent ev); void mouse_buttondown(XButtonPressedEvent ev); }; class x11ui : public interface { xconnection conn; XAtom4 game_app; public: x11ui(char *xserver, atom4 *game, eventloop *loop, int *exitflag); ~x11ui(); }; #endif // XATOM4_H atom4/x/xtriboard.h0100600000175000017500000000361207633531443014071 0ustar hsteohhsteoh/* * X11 interface for Atom-4 game board * Header file * * $Id: xtriboard.h,v 1.14 2003/03/12 04:09:55 hsteoh Exp hsteoh $ */ #ifndef XTRIBOARD_H #define XTRIBOARD_H #include #include #include "game.h" #include "triboard.h" #include "xcursor.h" #include "xsprite.h" #include "xutil.h" #define CELL_WIDTH 24 #define CELL_HEIGHT 21 #define NUM_BALLS 8 class xtriboard : public xwindow { int cell_wd, cell_dp; // dimensions (in cells) xsprite_engine *eng; // [R] Pixmap buffer; // drawing buffer tiled_bckgnd *tritile; // tiled background // FIXME: this should be moved to class XAtom4 static char *ballnames[NUM_BALLS]; xflatsprite *balls[8]; // Auto-positioning cursor xcursor *cursor; // TESTING: ideally, this should be done in class xatom4 atom4 *game; // [R] // Cell coordinate calculations inline int real_xcoor(int x, int y) { return x*CELL_WIDTH + (y%2+1)*CELL_WIDTH/2; } inline int real_ycoor(int x, int y) { return (y+1)*CELL_HEIGHT; } inline void draw_at(int x, int y, xflatsprite *spr) { spr->draw(buffer, real_xcoor(x,y), real_ycoor(x,y)); } // compute board coors from scrn coors static void drawcell(int bx, int by, celltype cell, void *context); public: // Note: width, depth are in units of cells. xtriboard(xconnection *conn, xsprite_engine *eng, xwindow *parent, atom4 *game, int x, int y); ~xtriboard(); // Convert screen coors to board coors void calc_bcoor(int rx, int ry, int &bx, int &by); void cursor_on() { cursor->on(); } void cursor_off() { cursor->off(); } void set_cursor(celltype tile) { cursor->set_sprite(balls[tile-'a']); } // Maintenance void refresh(); // Event handlers void expose(XExposeEvent ev); void mouse_enter(XEnterWindowEvent ev); void mouse_leave(XLeaveWindowEvent ev); void mouse_move(XMotionEvent ev); }; #endif // XTRIBOARD_H atom4/x/xtriboard.cc0100600000175000017500000001447707633766026014251 0ustar hsteohhsteoh/* * X11 interface for Atom-4 game board * Implementation file * * $Id: xtriboard.cc,v 1.24 2003/03/13 02:25:17 hsteoh Exp hsteoh $ */ #include "exception.h" #include "xtriboard.h" #define BORDER_WIDTH 1 #define CURSOR_Y_OFFSET -3 // offset from piece already on board #ifndef DATADIR // should be defined by build script #define DATADIR "." // search current dir if undefined #endif #define TILE_XPM DATADIR "/tritile12.xpm" char *xtriboard::ballnames[NUM_BALLS]={ DATADIR "/blackball12.xpm", DATADIR "/redball12.xpm", DATADIR "/greenball12.xpm", DATADIR "/yellowball12.xpm", DATADIR "/blueball12.xpm", DATADIR "/purpleball12.xpm", DATADIR "/turqball12.xpm", DATADIR "/whiteball12.xpm" }; // To simplify the calculation, we approximate the marble's area as a hexagon // bounded by the perpendiculars to our tile boundaries, and use linear // equations to calculate our position. void xtriboard::calc_bcoor(int rx, int ry, int &bx, int &by) { int xbase = rx / CELL_WIDTH; int xrem = rx % CELL_WIDTH; int ybase = ry / CELL_HEIGHT; int yrem = ry % CELL_HEIGHT; // Notes: // - the equations we use are based on the fact that an equilateral triangle // of w units per side has its center point at a distance of w/sqrt(3) // units from each side. // - we're using 173/100 as an approximation of sqrt(3). // - because we're shifting the entire board down by (1/2,1/2) so that // marbles at (0,0) are not clipped, our (bx,by) calculations are // skewed by (-1,-1) so that they map correctly to real board coors. if (ybase%2 == 0) { // even row (we number rows from 0) if (xrem < CELL_WIDTH/2) { // upperleft quadrant // Determine which side of y = (CELL_WIDTH-x)/sqrt(3) we're on if (173*yrem < 100*(CELL_WIDTH-xrem)) { // in upperleft tritant bx = xbase-1; by = ybase-1; return; } } else { // upperright quadrant // Determine which side of y = x/sqrt(3) we're on if (173*yrem < 100*xrem) { // in upperright tritant bx = xbase; by = ybase-1; return; } } // Neither; we must be in lower tritant (center marble) bx = xbase; by = ybase; } else { // odd row if (xrem < CELL_WIDTH/2) { // lowerleft quadrant // Determine which side of (CELL_HEIGHT-y) = (CELL_WIDTH-x)/sqrt(3) // we're on if (173*(CELL_HEIGHT-yrem) < 100*(CELL_WIDTH-xrem)) { // in lowerleft tritant bx = xbase-1; by = ybase; return; } } else { // Determine which side of (CELL_HEIGHT-y) = x/sqrt(3) we're on if (173*(CELL_HEIGHT-yrem) < 100*xrem) { // in lowerright tritant bx = xbase; by = ybase; return; } } // Neither; we must be in upper tritant (center marble) bx = xbase; by = ybase-1; } // endelse(odd/even row) } // Notes: // - we add half a cell width to the width because odd rows are shifted to // the right by half a cell's width. // - on top of this, we need to add one full cell's width, to give room for // marbles at the edge of the board not to be clipped. // - similarly, for the height, we need to add one full cell's height xtriboard::xtriboard(xconnection *connection, xsprite_engine *engine, xwindow *parent, atom4 *g, int x, int y) : xwindow(connection, parent, x,y, CELL_WIDTH*g->board_width() + CELL_WIDTH/2, CELL_HEIGHT*(g->board_height()+1), BORDER_WIDTH, connection->black(), connection->black()), eng(engine), game(g) { Display *disp = conn->display(); cell_wd = g->board_width(); cell_dp = g->board_height(); // Create window buffer ((wd,ht) set up by base ctor) buffer = XCreatePixmap(disp, win, wd, ht, conn->scrn_depth()); tritile=NULL; try { int i; tritile=new tiled_bckgnd(conn, buffer, TILE_XPM); tritile->paint(0,0, wd,ht); // (The following is for easier exception handling) for (i=0; icurrent_tile()-'a'], CELL_WIDTH, CELL_HEIGHT*2); // Select input that we're interested in XSelectInput(disp, win, ExposureMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | FocusChangeMask); // Paint initial state to buffer game->get_board()->mapcell(drawcell, this); } catch(exception &e) { for (int i=0; idisplay(); delete cursor; for (int i=0; i='a' && cell<'a'+NUM_BALLS) { bp->draw_at(bx,by, bp->balls[cell-'a']); } } void xtriboard::refresh() { GC copygc = eng->get_copygc(); // Redraw screen tritile->paint(0,0, wd,ht); game->get_board()->mapcell(drawcell, this); XCopyArea(conn->display(), buffer, win, copygc, 0,0, width(),height(), 0,0); // Re-sync cursor, just in case current tile has changed cursor->set_sprite(balls[game->current_tile()-'a']); } void xtriboard::expose(XExposeEvent ev) { int x=ev.x, y=ev.y, w=ev.width, h=ev.height; GC copygc = eng->get_copygc(); XCopyArea(ev.display, buffer, win, copygc, x,y, w,h, x,y); } void xtriboard::mouse_enter(XEnterWindowEvent ev) { // nothing for now } void xtriboard::mouse_leave(XLeaveWindowEvent ev) { cursor->off(); // turn off cursor when mouse leaves } void xtriboard::mouse_move(XMotionEvent ev) { int x=ev.x, y=ev.y; int bx,by; if (game->is_local_turn()) { calc_bcoor(x,y,bx,by); if (game->check_legal(bx,by)) { cursor->move(real_xcoor(bx,by), real_ycoor(bx,by)+CURSOR_Y_OFFSET); cursor->on(); return; } } // Not in a valid position, not our turn, or round is over, so hide the // auto cursor cursor->off(); } atom4/x/blueball12.xpm0100600000175000017500000000152607604751016014375 0ustar hsteohhsteoh/* XPM */ static char * small_redball_xpm[] = { "24 24 8 1 12 12", " c None", ". c dark blue", "X c #2020E0", "o c #a0a0f0", "O c white", "+ c #5050e0", "@ c #0000d0", "# c blue", " ...... ", " .......... ", " .Xoo.......... ", " .oOO+........... ", " .oOOo............. ", " .oOO+............... ", " +OO+................ ", " .OOo.................. ", " .OX................... ", "..X.....................", "........................", "........................", "........................", "......................@.", ".....................@@.", " ...................@#. ", " ...................@#. ", " .................@#. ", " ................@##. ", " ..............@##. ", " ...........@###. ", " ...#@@@@####.. ", " ..######.. ", " ...... "}; atom4/x/purpleball12.xpm0100600000175000017500000000152407604753571014763 0ustar hsteohhsteoh/* XPM */ static char * purpleball_xpm[] = { "24 24 8 1 12 12", " c None", ". c #8b008b", "X c #E020E0", "o c #f0a0f0", "O c white", "+ c #e050e0", "@ c #d000d0", "# c #ff00ff", " ...... ", " .......... ", " .Xoo.......... ", " .oOO+........... ", " .oOOo............. ", " .oOO+............... ", " +OO+................ ", " .OOo.................. ", " .OX................... ", "..X.....................", "........................", "........................", "........................", "......................@.", ".....................@@.", " ...................@#. ", " ...................@#. ", " .................@#. ", " ................@##. ", " ..............@##. ", " ...........@###. ", " ...#@@@@####.. ", " ..######.. ", " ...... "}; atom4/x/tritile12.xpm0100600000175000017500000000244507604751700014270 0ustar hsteohhsteoh/* XPM */ static char * tritile12_xpm[] = { "24 42 6 1", " c #606060", ". c white", "X c grey", "o c black", "O c #e0e0e0", "+ c #d0d0d0", " ...................... ", "XoOXXXXXXXXXXXXXXXXXXX +", "X OXXXXXXXXXXXXXXXXXXXo.", "XXoOXXXXXXXXXXXXXXXXX .X", "XX OXXXXXXXXXXXXXXXXXo.X", "XXXoOXXXXXXXXXXXXXXX .XX", "XXX OXXXXXXXXXXXXXXXo.XX", "XXXXoOXXXXXXXXXXXXXo.XXX", "XXXXXoOXXXXXXXXXXX ++XXX", "XXXXX OXXXXXXXXXXXo.XXXX", "XXXXXXoOXXXXXXXXX .XXXXX", "XXXXXX OXXXXXXXXXo.XXXXX", "XXXXXXXoOXXXXXXX .XXXXXX", "XXXXXXX OXXXXXXXo.XXXXXX", "XXXXXXXXoOXXXXXo.XXXXXXX", "XXXXXXXXXoOXXX ++XXXXXXX", "XXXXXXXXX OXXXo.XXXXXXXX", "XXXXXXXXXXoOX .XXXXXXXXX", "XXXXXXXXXX OXo.XXXXXXXXX", "XXXXXXXXXXX .XXXXXXXXXX", "oooooooooooooooooooooooo", "........... ...........", "XXXXXXXXXX .XoOXXXXXXXXX", "XXXXXXXXXXo.X OXXXXXXXXX", "XXXXXXXXX .XXXoOXXXXXXXX", "XXXXXXXXXo.XXX OXXXXXXXX", "XXXXXXXXo.XXXXXoOXXXXXXX", "XXXXXXX ++XXXXXXoOXXXXXX", "XXXXXXXo.XXXXXXX OXXXXXX", "XXXXXX .XXXXXXXXXoOXXXXX", "XXXXXXo.XXXXXXXXX OXXXXX", "XXXXX .XXXXXXXXXXXoOXXXX", "XXXXXo.XXXXXXXXXXX OXXXX", "XXXXo.XXXXXXXXXXXXXoOXXX", "XXX ++XXXXXXXXXXXXXXoOXX", "XXXo.XXXXXXXXXXXXXXX OXX", "XX .XXXXXXXXXXXXXXXXXoOX", "XXo.XXXXXXXXXXXXXXXXX OX", "X .XXXXXXXXXXXXXXXXXXXoO", "Xo.XXXXXXXXXXXXXXXXXXX O", " .XXXXXXXXXXXXXXXXXXXXX ", " oooooooooooooooooooooo "}; atom4/x/redball12.xpm0100600000175000017500000000152407604750731014221 0ustar hsteohhsteoh/* XPM */ static char * small_redball_xpm[] = { "24 24 8 1 12 12", " c None", ". c dark red", "X c #E02020", "o c #f0a0a0", "O c white", "+ c #e05050", "@ c #d00000", "# c red", " ...... ", " .......... ", " .Xoo.......... ", " .oOO+........... ", " .oOOo............. ", " .oOO+............... ", " +OO+................ ", " .OOo.................. ", " .OX................... ", "..X.....................", "........................", "........................", "........................", "......................@.", ".....................@@.", " ...................@#. ", " ...................@#. ", " .................@#. ", " ................@##. ", " ..............@##. ", " ...........@###. ", " ...#@@@@####.. ", " ..######.. ", " ...... "}; atom4/x/turqball12.xpm0100600000175000017500000000152207604753744014447 0ustar hsteohhsteoh/* XPM */ static char * turqball_xpm[] = { "24 24 8 1 12 12", " c None", ". c #008b8b", "X c #20E0E0", "o c #a0f0f0", "O c white", "+ c #50e0e0", "@ c #00d0d0", "# c #00ffff", " ...... ", " .......... ", " .Xoo.......... ", " .oOO+........... ", " .oOOo............. ", " .oOO+............... ", " +OO+................ ", " .OOo.................. ", " .OX................... ", "..X.....................", "........................", "........................", "........................", "......................@.", ".....................@@.", " ...................@#. ", " ...................@#. ", " .................@#. ", " ................@##. ", " ..............@##. ", " ...........@###. ", " ...#@@@@####.. ", " ..######.. ", " ...... "}; atom4/x/wheel12.xpm0100600000175000017500000001300207625152642013712 0ustar hsteohhsteoh/* XPM */ static char * wheel12_xpm[] = { "72 66 38 1 12 12", " c None", ". c dark red", "X c #8b8b00", "o c #E02020", "O c #f0a0a0", "+ c #E0E020", "@ c #f0f0a0", "# c white", "$ c #e05050", "% c #e0e050", "& c #d00000", "* c #d0d000", "= c red", "- c yellow", "; c #8b008b", ": c #008b00", "> c #E020E0", ", c #f0a0f0", "< c #20E020", "1 c #a0f0a0", "2 c #e050e0", "3 c #50e050", "4 c #d000d0", "5 c #00d000", "6 c magenta", "7 c green", "8 c dark blue", "9 c #008b8b", "0 c #2020E0", "q c #a0a0f0", "w c #20E0E0", "e c #a0f0f0", "r c #5050e0", "t c #50e0e0", "y c #0000d0", "u c #00d0d0", "i c blue", "p c cyan", " ...... XXXXXX ", " .......... XXXXXXXXXX ", " .oOO.......... X+@@XXXXXXXXXX ", " .O##$........... X@##%XXXXXXXXXXX ", " .O##O............. X@##@XXXXXXXXXXXXX ", " .O##$............... X@##%XXXXXXXXXXXXXXX ", " $##$................ %##%XXXXXXXXXXXXXXXX ", " .##O.................. X##@XXXXXXXXXXXXXXXXXX ", " .#o................... X#+XXXXXXXXXXXXXXXXXXX ", " ..o.....................XX+XXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ......................&.XXXXXXXXXXXXXXXXXXXXXX*X ", " .....................&&.XXXXXXXXXXXXXXXXXXXXX**X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " .................&=. XXXXXXXXXXXXXXXXX*-X ", " ................&==. XXXXXXXXXXXXXXXX*--X ", " ..............&==. XXXXXXXXXXXXXX*--X ", " ...........&===. XXXXXXXXXXX*---X ", " ;;;;;; ...=&&&&====.. XXX-****----XX :::::: ", " ;;;;;;;;;; ..======.. XX------XX :::::::::: ", " ;>,,;;;;;;;;;; ...... XXXXXX :<11:::::::::: ", " ;,##2;;;;;;;;;;; :1##3::::::::::: ", " ;,##,;;;;;;;;;;;;; :1##1::::::::::::: ", " ;,##2;;;;;;;;;;;;;;; :1##3::::::::::::::: ", " 2##2;;;;;;;;;;;;;;;; 3##3:::::::::::::::: ", " ;##,;;;;;;;;;;;;;;;;;; :##1:::::::::::::::::: ", " ;#>;;;;;;;;;;;;;;;;;;; :#<::::::::::::::::::: ", ";;>;;;;;;;;;;;;;;;;;;;;; ::<:::::::::::::::::::::", ";;;;;;;;;;;;;;;;;;;;;;;; ::::::::::::::::::::::::", ";;;;;;;;;;;;;;;;;;;;;;;; ::::::::::::::::::::::::", ";;;;;;;;;;;;;;;;;;;;;;;; ::::::::::::::::::::::::", ";;;;;;;;;;;;;;;;;;;;;;4; ::::::::::::::::::::::5:", ";;;;;;;;;;;;;;;;;;;;;44; :::::::::::::::::::::55:", " ;;;;;;;;;;;;;;;;;;;46; :::::::::::::::::::57: ", " ;;;;;;;;;;;;;;;;;;;46; :::::::::::::::::::57: ", " ;;;;;;;;;;;;;;;;;46; :::::::::::::::::57: ", " ;;;;;;;;;;;;;;;;466; ::::::::::::::::577: ", " ;;;;;;;;;;;;;;466; ::::::::::::::577: ", " ;;;;;;;;;;;4666; :::::::::::5777: ", " ;;;644446666;; 888888 999999 :::755557777:: ", " ;;666666;; 8888888888 9999999999 ::777777:: ", " ;;;;;; 80qq8888888888 9wee9999999999 :::::: ", " 8q##r88888888888 9e##t99999999999 ", " 8q##q8888888888888 9e##e9999999999999 ", " 8q##r888888888888888 9e##t999999999999999 ", " r##r8888888888888888 t##t9999999999999999 ", " 8##q888888888888888888 9##e999999999999999999 ", " 8#08888888888888888888 9#w9999999999999999999 ", " 88088888888888888888888899w999999999999999999999 ", " 888888888888888888888888999999999999999999999999 ", " 888888888888888888888888999999999999999999999999 ", " 888888888888888888888888999999999999999999999999 ", " 8888888888888888888888y89999999999999999999999u9 ", " 888888888888888888888yy8999999999999999999999uu9 ", " 8888888888888888888yi8 9999999999999999999up9 ", " 8888888888888888888yi8 9999999999999999999up9 ", " 88888888888888888yi8 99999999999999999up9 ", " 8888888888888888yii8 9999999999999999upp9 ", " 88888888888888yii8 99999999999999upp9 ", " 88888888888yiii8 99999999999uppp9 ", " 888iyyyyiiii88 999puuuupppp99 ", " 88iiiiii88 99pppppp99 ", " 888888 999999 "}; atom4/x/TODO0100600000175000017500000000730407643725230012414 0ustar hsteohhsteoh- Need a resource manager for shared things like common GC's and pixmaps. - Implement a resource-based system for keeping track of XPM paths, mapping resource names to files, configuration system, etc.. Currently this is just a lot of hardcoded ugliness and klunky local parametrization. - for runtime efficiency, consider using a compile-time generated .h file that compiles resource names into #define's, and use integer-based runtime resource ID's instead of strings. - we should separate static resource management and dynamic resource management. For static management, everything should be resolved at compile time and compiled into the program; for dynamic management, we'll need a real name-to-resource mapper, runtime loaders, etc.. - implement common algo's in a base class, and use derived classes to make a GC manager, a pixmap manager, etc.. Don't over-generalize already!! - Need to implement generic font-handling class(es) in xutil (or use a separate module - xtext - so that the core X11 utils are independent of any font-handling ugliness) - class xwindow should provide input selection funcs (eg., select_kbd(), select_mouse(), etc.) so that we can make sure resizes are always handled, e.g., so that we can update window dimensions properly. - maybe provide single wrapper around XSelectInput()? - Need to properly do an XMatchVisualInfo so that we fail correctly on color-challenged displays. Don't want users to get identical grey marbles on 2-bit displays :-) + add facility for event handlers to indicate program quit. Quitting using an exception is lame :-) + Need to fix xwindow::{width,height}(). Currently, they do not take into account the width of the border. + probably need two sets of dimensions access funcs; one for internal dim and one for external dim ('cos some stuff does need to know exactly how big the internal part of the window is). + Need to handle keyboard input processing (must put this in xutil, since it gets quite ugly with KeySym mappings and whatnot) * Let's stick with local KeySym handling until we come up with a better framework for handling keystrokes. + Integrate with class triboard so that we can have a real underlying representation for things (makes it easier for experimenting) + Integrate with main game engine. (Yeah, so this will break the ncurses UI, but who cares? :-P) + Integrate into main binary already!!! + Need to properly implement scoreboard panel so that we can see the progress of the game! :-) + actually keep score + show player turns + show winner + Need to re-do event-handling framework between XAtom4 subwindows. What we *really* want is to have the child windows ONLY process background tasks like exposures, mouse tracking, etc., events that cause game state changes should be handled by class xatom4. The subwindows should have a "slave" interface driven by class xatom4, not the other way round. X make xscoreboard independent of class atom4 + move xtriboard button handler into class XAtom4 * might be hard to make xtriboard completely independent of class atom4, 'cos there are too many gamestate-specific stuff in there. Unless we *totally* move all event handlers (eg. the cursor handling) into class XAtom4. + BUG: in AI mode, after you win, the autocursor still shows up when you mouse over "legal" moves. * Was actually bug in atom4ai::is_local_turn(). M Need to generalize xsprite_engine: M what we *really* want is a graphics manager which is independent of server-side Pixmaps; M *and* another per-server, per-screen, -specific engine where server dependent GCs, Pixmaps, etc., can be kept. + Fix refresh code in xscoreboard: it's very inefficient!! atom4/x/xscoreboard.h0100600000175000017500000000224307643725116014410 0ustar hsteohhsteoh/* * Atom-4 X11 client: scoreboard panel * Header file * * $Id: xscoreboard.h,v 1.5 2003/04/06 04:16:48 hsteoh Exp hsteoh $ */ #ifndef XSCOREBOARD_H #define XSCOREBOARD_H #include "game.h" #include "xsprite.h" #include "xutil.h" #define NUM_WHEELS 6 class xscoreboard : public xwindow { atom4 *game; // [R] xsprite_engine *eng; // [R] Pixmap buffer; // poor man's backing store :-) GC erasegc; xflatsprite *wheels[NUM_WHEELS]; // [[O]] XFontStruct *titlefont; // Title font GC titlegc; XFontStruct *scorefont; // regular font GC textgc; void draw_center(XFontStruct *font, GC gc, char *text, int y); void render_score(int player, int y); void render_wheel(int wheel); public: // Note: the width*height here is the *external* width and height (including // the border), so that we can "fit" this panel into an exact amount of // external space without worrying about adjusting it for border widths. xscoreboard(xconnection *conn, xsprite_engine *eng, xwindow *parent, atom4 *game, int x, int y, int width, int height); ~xscoreboard(); void refresh(); void expose(XExposeEvent ev); }; #endif // XSCOREBOARD_H atom4/x/xscoreboard.cc0100600000175000017500000001304107647077466014560 0ustar hsteohhsteoh/* * Atom-4 X11 client: scoreboard panel * Implementation file * * $Id: xscoreboard.cc,v 1.13 2003/04/15 21:52:48 hsteoh Exp hsteoh $ */ #include // DEBUG ONLY!!!! #include // DEBUG ONLY!!!! #include "color4.h" #include "exception.h" #include "interface.h" #include "xscoreboard.h" #define BORDER_WIDTH 3 // FIXME: we really should implement a sane font-handling framework #define TITLE_Y 0 // (this excludes the border) #define SCORES_START_Y 40 #define PL1_START_Y SCORES_START_Y #define PL2_START_Y (height() - PL_HEIGHT) #define PL_X 7 #define PL_HEIGHT 90 #define PL_TITLE_Y 0 // relative to start of score block #define PL_SCORE_Y 14 #define PL_MSG_Y 45 #define WHEEL_X (width()/2) #define WHEEL_Y (height()/2) // FIXME: we should implement a saner text-typesetting framework instead of // this ugly mess. We'll get to it some day...! :-) #define TEXTBUF_MAX 128 #ifndef DATADIR // should be defined by build script #define DATADIR "." // search current dir if undefined #endif static char *wheelnames[NUM_WHEELS] = { DATADIR "/wheel12-1.xpm", DATADIR "/wheel12-2.xpm", DATADIR "/wheel12-3.xpm", DATADIR "/wheel12-4.xpm", DATADIR "/wheel12-5.xpm", DATADIR "/wheel12-6.xpm" }; xscoreboard::xscoreboard(xconnection *conn, xsprite_engine *engine, xwindow *parent, atom4 *g, int x, int y, int w, int h) : xwindow(conn, parent, x,y, w-BORDER_WIDTH*2,h-BORDER_WIDTH*2, BORDER_WIDTH, conn->black(), conn->black()), game(g), eng(engine) { Display *disp = conn->display(); XGCValues gcvals; int i; for (i=0; iscrn_depth()); gcvals.foreground = conn->black(); erasegc = XCreateGC(disp, buffer, GCForeground, &gcvals); // TESTING CODE // Load a font to write in titlefont = XLoadQueryFont(disp, "9x15bold"); if (!titlefont) throw exception("Unable to load title font"); // GC for title font gcvals.foreground = conn->white(); gcvals.background = conn->black(); gcvals.font = titlefont->fid; titlegc = XCreateGC(disp, win, GCForeground | GCBackground | GCFont, &gcvals); scorefont = XLoadQueryFont(disp, "7x13"); if (!scorefont) throw exception("Unable to load font"); // GC for regular font gcvals.foreground = conn->white(); gcvals.background = conn->black(); gcvals.font = scorefont->fid; textgc = XCreateGC(disp, win, GCForeground | GCBackground | GCFont, &gcvals); // Do initial render into buffer refresh(); XSelectInput(conn->display(), win, ExposureMask | FocusChangeMask); } xscoreboard::~xscoreboard() { Display *disp = conn->display(); XFreeGC(disp, erasegc); XFreePixmap(disp, buffer); XFreeGC(disp, textgc); XFreeGC(disp, titlegc); XFreeFont(disp, scorefont); XFreeFont(disp, titlefont); } void xscoreboard::draw_center(XFontStruct *font, GC gc, char *text, int y) { XCharStruct result; int dummy, x; int textlen = strlen(text); // Measure extent of text XTextExtents(font, text, textlen, &dummy, &dummy, &dummy, &result); x = (wd - result.width)/2; XDrawString(conn->display(), buffer, gc, x, y+font->ascent, text, textlen); } void xscoreboard::render_score(int player, int starty) { Display *disp = conn->display(); char buf[TEXTBUF_MAX+1]; snprintf(buf, TEXTBUF_MAX, "Player %d", player); XDrawString(disp, buffer, textgc, PL_X, starty+PL_TITLE_Y+scorefont->ascent, buf, strlen(buf)); snprintf(buf, TEXTBUF_MAX, "Score: %d", game->score(player)); XDrawString(disp, buffer, textgc, PL_X, starty+PL_SCORE_Y+scorefont->ascent, buf, strlen(buf)); if (player==game->current_player()) { if (player==game->local_playernum()) { snprintf(buf, TEXTBUF_MAX, "*** YOUR TURN ***"); } else { snprintf(buf, TEXTBUF_MAX, "*** OPPONENT'S TURN ***"); } XDrawString(disp, buffer, textgc, PL_X, starty+PL_MSG_Y+scorefont->ascent, buf, strlen(buf)); } else if (player==game->winner()) { snprintf(buf, TEXTBUF_MAX, "!!! Player %d WINS !!!", player); XDrawString(disp, buffer, textgc, PL_X, starty+PL_MSG_Y+scorefont->ascent, buf, strlen(buf)); } else if (game->winner()==STALEMATE) { snprintf(buf, TEXTBUF_MAX, "=== DRAW: no more moves left ===", player); XDrawString(disp, buffer, textgc, PL_X, starty+PL_MSG_Y+scorefont->ascent, buf, strlen(buf)); } } void xscoreboard::render_wheel(int wheel) { if (wheel<0 || wheel>=NUM_WHEELS) throw exception("@Illegal wheel number: %d\n", wheel); wheels[wheel]->draw(buffer, WHEEL_X, WHEEL_Y); } void xscoreboard::refresh() { static int tiletowheel[8] = { -1, 0, 2, 1, 4, 5, 3, -1 }; int wheel = tiletowheel[game->current_tile()-'a']; // Erase any stuff that may be outdated XFillRectangle(conn->display(), buffer, erasegc, 0,0, width(), height()); // Render everything draw_center(titlefont, titlegc, "XAtom-4 " VERSION_STRING, TITLE_Y); render_wheel(wheel); render_score(1, PL1_START_Y); render_score(2, PL2_START_Y); // Copy to screen in case this update is being triggered internally XCopyArea(conn->display(), buffer, win, eng->get_copygc(), 0,0, wd,ht, 0,0); } void xscoreboard::expose(XExposeEvent ev) { XCopyArea(conn->display(), buffer, win, eng->get_copygc(), ev.x, ev.y, ev.width, ev.height, ev.x, ev.y); } atom4/x/wheel12-1.xpm0100600000175000017500000001305607631270136014055 0ustar hsteohhsteoh/* XPM */ static char * wheel12_1_xpm[] = { "72 66 41 1 36 33", " c None", ". c red", "X c #8b8b00", "o c #E02020", "O c #f0a0a0", "+ c #E0E020", "@ c #f0f0a0", "# c white", "$ c #e05050", "% c #e0e050", "& c #d0d000", "* c yellow", "= c #8b008b", "- c #e0e0e0", "; c #008b00", ": c #E020E0", "> c #f0a0f0", ", c #e8e8e8", "< c #f8f8f8", "1 c #20E020", "2 c #a0f0a0", "3 c #e050e0", "4 c #f0f0f0", "5 c #50e050", "6 c black", "7 c #d000d0", "8 c #00d000", "9 c magenta", "0 c green", "q c dark blue", "w c #008b8b", "e c #2020E0", "r c #a0a0f0", "t c #20E0E0", "y c #a0f0f0", "u c #5050e0", "i c #50e0e0", "p c #0000d0", "a c #00d0d0", "s c blue", "d c cyan", " ...... XXXXXX ", " .......... XXXXXXXXXX ", " .oOO.......... X+@@XXXXXXXXXX ", " .O##$........... X@##%XXXXXXXXXXX ", " .O##O............. X@##@XXXXXXXXXXXXX ", " .O##$............... X@##%XXXXXXXXXXXXXXX ", " $##$................ %##%XXXXXXXXXXXXXXXX ", " .##O.................. X##@XXXXXXXXXXXXXXXXXX ", " .#o................... X#+XXXXXXXXXXXXXXXXXXX ", " ..o.....................XX+XXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXX&X ", " ........................XXXXXXXXXXXXXXXXXXXXX&&X ", " ...................... XXXXXXXXXXXXXXXXXXX&*X ", " ...................... XXXXXXXXXXXXXXXXXXX&*X ", " .................... XXXXXXXXXXXXXXXXX&*X ", " .................... XXXXXXXXXXXXXXXX&**X ", " .................. XXXXXXXXXXXXXX&**X ", " ................ XXXXXXXXXXX&***X ", " ====== .............. ------ XXX*&&&&****XX ;;;;;; ", " ========== .......... ---------- XX******XX ;;;;;;;;;; ", " =:>>========== ...... -,<<---------- XXXXXX ;122;;;;;;;;;; ", " =>##3=========== -<##4----------- ;2##5;;;;;;;;;;; ", " =>##>============= -<##<------------- ;2##2;;;;;;;;;;;;; ", " =>##3=============== -<##4--------------- ;2##5;;;;;;;;;;;;;;; ", " 3##3================ 4##4------6--------- 5##5;;;;;;;;;;;;;;;; ", " =##>================== -##<------66---------- ;##2;;;;;;;;;;;;;;;;;; ", " =#:=================== -#,-----6666---------- ;#1;;;;;;;;;;;;;;;;;;; ", "==:=====================--,--------66-----------;;1;;;;;;;;;;;;;;;;;;;;;", "========================-----------66-----------;;;;;;;;;;;;;;;;;;;;;;;;", "========================-----------66-----------;;;;;;;;;;;;;;;;;;;;;;;;", "========================-----------66-----------;;;;;;;;;;;;;;;;;;;;;;;;", "======================7=-----------66---------4-;;;;;;;;;;;;;;;;;;;;;;8;", "=====================77=-----------66--------44-;;;;;;;;;;;;;;;;;;;;;88;", " ===================79= ----------66-------4#- ;;;;;;;;;;;;;;;;;;;80; ", " ===================79= --------666666-----4#- ;;;;;;;;;;;;;;;;;;;80; ", " =================79= -----------------4#- ;;;;;;;;;;;;;;;;;80; ", " ================799= ----------------4##- ;;;;;;;;;;;;;;;;800; ", " ==============799= --------------4##- ;;;;;;;;;;;;;;800; ", " ===========7999= -----------4###- ;;;;;;;;;;;8000; ", " ===977779999== qqqqqq ---#4444####-- wwwwww ;;;088880000;; ", " ==999999== qqqqqqqqqq --######-- wwwwwwwwww ;;000000;; ", " ====== qerrqqqqqqqqqq ------ wtyywwwwwwwwww ;;;;;; ", " qr##uqqqqqqqqqqq wy##iwwwwwwwwwww ", " qr##rqqqqqqqqqqqqq wy##ywwwwwwwwwwwww ", " qr##uqqqqqqqqqqqqqqq wy##iwwwwwwwwwwwwwww ", " u##uqqqqqqqqqqqqqqqq i##iwwwwwwwwwwwwwwww ", " q##rqqqqqqqqqqqqqqqqqq w##ywwwwwwwwwwwwwwwwww ", " q#eqqqqqqqqqqqqqqqqqqq w#twwwwwwwwwwwwwwwwwww ", " qqeqqqqqqqqqqqqqqqqqqqqqwwtwwwwwwwwwwwwwwwwwwwww ", " qqqqqqqqqqqqqqqqqqqqqqqqwwwwwwwwwwwwwwwwwwwwwwww ", " qqqqqqqqqqqqqqqqqqqqqqqqwwwwwwwwwwwwwwwwwwwwwwww ", " qqqqqqqqqqqqqqqqqqqqqqqqwwwwwwwwwwwwwwwwwwwwwwww ", " qqqqqqqqqqqqqqqqqqqqqqpqwwwwwwwwwwwwwwwwwwwwwwaw ", " qqqqqqqqqqqqqqqqqqqqqppqwwwwwwwwwwwwwwwwwwwwwaaw ", " qqqqqqqqqqqqqqqqqqqpsq wwwwwwwwwwwwwwwwwwwadw ", " qqqqqqqqqqqqqqqqqqqpsq wwwwwwwwwwwwwwwwwwwadw ", " qqqqqqqqqqqqqqqqqpsq wwwwwwwwwwwwwwwwwadw ", " qqqqqqqqqqqqqqqqpssq wwwwwwwwwwwwwwwwaddw ", " qqqqqqqqqqqqqqpssq wwwwwwwwwwwwwwaddw ", " qqqqqqqqqqqpsssq wwwwwwwwwwwadddw ", " qqqsppppssssqq wwwdaaaaddddww ", " qqssssssqq wwddddddww ", " qqqqqq wwwwww "}; atom4/x/logo.xpm0100600000175000017500000000545707606110330013405 0ustar hsteohhsteoh/* XPM */ static char * logo_xpm[] = { "96 24 28 1", " c None", ". c #8b008b", "X c white", "o c #f0a0f0", "O c dark green", "+ c #50e050", "@ c dark red", "# c #c0c0c0", "$ c #606060", "% c #00d000", "& c magenta", "* c #f0a0a0", "= c #e05050", "- c #a0f0a0", "; c #d000d0", ": c #404040", "> c green", ", c #8b8b00", "< c #008b8b", "1 c #d00000", "2 c #808080", "3 c #f0f0a0", "4 c #a0f0f0", "5 c red", "6 c yellow", "7 c cyan", "8 c #d0d000", "9 c #00d0d0", " ", " .... ", " ........ ", " ..XXo..... OOOO ", " .XXo...... O+OOOO ", " ..o......... XXXXXXXXXXXXXXX @@@@ XXXXXXXXXXXXXXXXX OXXOOOO ", " ............ #XXXXXXXXXXXXX$ @@@@@@@@ #XXXXXXXXXXXXXXX$ OX+XOO%O ", " ..........&. ##XXXXXXXXXXX$$ @@*X=@@@@@ ##XXXXXXXXXXXXX$$ O-+O-OO%O ", " .........;&. ###::::#::::$$$ @=XX*@@@@@@@ ###::::::::::#$$$ O-+OO-OO%O ", " .......;&. ##::::##$::::$$@@X*=@@@@@@@@@###$:::#$:::##$$$ O-+OOO-OO>O ", " .....;;&&. #::::###$$::::$@**=@@@@@@@@@@###$$:##$$:###$$$ O-+OO%O-OO>O ", " ,,,,..&&&&..<<<< ###$$$ @@@@@@@@@@@@@@@@##$$$##$$$###$$$XXXXXXXXXX O-+OO%>O-OO>O ", " ,,,,,,,,....<<<<<<<< ###$$$ @@@@@@@@@@@@@1@@##$$$##$$$###$$$#XXXXXXXX2 O-+OO%> OXOO>O ", " ,,XX3,,,,, <O ", " ,,,,,,,,,,6,<<<<<<<<<<7< ###$$$ @@@@@@11155@ ###$$$##$$$###$$$ OO%%%>>>O-OO%>O ", " ,,,,,,,,,86,<<<<<<<<<97< ###$$$ @@1111555@ ###$$$##$$$###$$$ O-OOOO ", " ,,,,,,,86, <<<<<<<97< ###$$$ @@5555@@ ###$$$ ###$$$ OOOO%O ", " ,,,,,8866, <<<<<9977< ###$$$ @@@@ ###$$$ ###$$$ OOO%>O ", " ,,6666,, <<7777<< %>>O ", " ,,,, <<<< ", " "}; atom4/x/logo.png0100600000175000017500000000223007606371606013366 0ustar hsteohhsteoh‰PNG  IHDR`Uy$×bKGDÿÿÿÿÿÿ X÷ÜMIDATxÚíš»n«@†—£Óç"Åoà*µiÜÛEjc)îR²] ,¥ƒòt¸€:…éÓØuš<‚-åÌpŠÉݰì…粟„,˜eáŸË&ÄbùÅ8}CB°(xû)%„R§·ñ-Œ P$xÖ!,— ³àXáû~Qøþyž§ižÇžÇb{Ö,jÜßÒx7Bøzúcê2º ßbQ„?›=>Îfâãx{ÿêÏF~S‚G»¼LP|`øn q>¹þ¶N)XÙ¹Ôo;þáAƪ1èŠïÃ&Kß‘>~_­’$I , Bîîf³öÔÜlgèyªÎ{x@øIEIr~–==e!išçiªo]»2Åt ›.( ‚ à‡ûEÇF£ÑhÄß‚EßÜl·Ûíyž§išòüœeY&?´Ë+‘dç© ÚKpà¯CUøYv:eÙYø¦ÐÎ]»5(üñ¶(‚MÕ®¬ð‘À‡Ãáp¨ÿÎFj> þxœÏçs(=O÷®ˆ3‚èúq¾²Ç}=L _îèÁJ <+|]T…Ï¢ëXê ðO'ˆôª¿+ìõóæ+{Ü呾\m/‹t †aÈ‹L¶)…èˆjzŒøgç³›_¨QJ©ã¸®ëº®l†/JQ PÔí²¹k„—åõÕu]—ÛÛÝn·_¿ì¯úf˜›ô…/‡¾ð]:`C¢»vØí „«;°ÝÞÜ@ žºn[3 ÂÏ(UGøt6ò›<Ú¥ëiÀœàûÍØ–”}Ñ% +|l`äÕþC¡¶æâƒŽ€tœÕÊÄÓZ,âJ ßo×)–RÕŒÂ홊ô<~[¤Gx·ÈRí‰3Fûâ¹·6hµÀùž#”‘ý ^$|Œüºã|xÿÎD¡²Ÿ××ÕOÙ.+|\H®t™Nû®jû¡ ^I$º&^¤ÿ=Âgaa³Q;_´fÏÍŽ÷騭Qo(ËñuÁ£-wà!³íL# /Ö°})ÊJlwÇ”ðekuÏó<Ï»ü¿LMÁ¯ý›k~í ÀÆa”¥9úÁÍîþwh‹.j/̸¢é}@›!ŒÇH½ÿß4BY(:#X!YÞÞ@§¬à›k~i‰J#±àeFhKÍVð‹Åb”ÿßc7£ïQøIEND®B`‚atom4/x/greenball12.xpm0100600000175000017500000000152307617742621014551 0ustar hsteohhsteoh/* XPM */ static char * greenball_xpm[] = { "24 24 8 1 12 12", " c None", ". c #008b00", "X c #20E020", "o c #a0f0a0", "O c white", "+ c #50e050", "@ c #00d000", "# c #00ff00", " ...... ", " .......... ", " .Xoo.......... ", " .oOO+........... ", " .oOOo............. ", " .oOO+............... ", " +OO+................ ", " .OOo.................. ", " .OX................... ", "..X.....................", "........................", "........................", "........................", "......................@.", ".....................@@.", " ...................@#. ", " ...................@#. ", " .................@#. ", " ................@##. ", " ..............@##. ", " ...........@###. ", " ...#@@@@####.. ", " ..######.. ", " ...... "}; atom4/x/yellowball12.xpm0100600000175000017500000000152407617743043014764 0ustar hsteohhsteoh/* XPM */ static char * yellowball_xpm[] = { "24 24 8 1 12 12", " c None", ". c #8b8b00", "X c #E0E020", "o c #f0f0a0", "O c white", "+ c #e0e050", "@ c #d0d000", "# c #ffff00", " ...... ", " .......... ", " .Xoo.......... ", " .oOO+........... ", " .oOOo............. ", " .oOO+............... ", " +OO+................ ", " .OOo.................. ", " .OX................... ", "..X.....................", "........................", "........................", "........................", "......................@.", ".....................@@.", " ...................@#. ", " ...................@#. ", " .................@#. ", " ................@##. ", " ..............@##. ", " ...........@###. ", " ...#@@@@####.. ", " ..######.. ", " ...... "}; atom4/x/xtext.h0100600000175000017500000000014407620321070013231 0ustar hsteohhsteoh/* * X11 Text-handling utilities * $Id$ */ #ifndef XTEXT_H #define XTEXT_H #endif // XTEXT_H atom4/x/blackball12.xpm0100600000175000017500000000152307621074426014521 0ustar hsteohhsteoh/* XPM */ static char * blackball12_xpm[] = { "24 24 8 1 12 12", " c None", ". c #303030", "X c #c0c0c0", "o c #e0e0e0", "O c white", "+ c #a0a0a0", "@ c #202020", "# c black", " ...... ", " .......... ", " .Xoo.......... ", " .oOO+........... ", " .oOOo............. ", " .oOO+............... ", " +OO+................ ", " .OOo.................. ", " .OX................... ", "..X.....................", "........................", "........................", "........................", "......................@.", ".....................@@.", " ...................@#. ", " ...................@#. ", " .................@#. ", " ................@##. ", " ..............@##. ", " ...........@###. ", " ...#@@@@####.. ", " ..######.. ", " ...... "}; atom4/x/whiteball12.xpm0100600000175000017500000000152507621246252014565 0ustar hsteohhsteoh/* XPM */ static char * blackball12_xpm[] = { "24 24 8 1 12 12", " c None", ". c #e0e0e0", "X c #e8e8e8", "o c #f8f8f8", "O c white", "+ c #f0f0f0", "@ c #f0f0f0", "# c #ffffff", " ...... ", " .......... ", " .Xoo.......... ", " .oOO+........... ", " .oOOo............. ", " .oOO+............... ", " +OO+................ ", " .OOo.................. ", " .OX................... ", "..X.....................", "........................", "........................", "........................", "......................@.", ".....................@@.", " ...................@#. ", " ...................@#. ", " .................@#. ", " ................@##. ", " ..............@##. ", " ...........@###. ", " ...#@@@@####.. ", " ..######.. ", " ...... "}; atom4/x/xcursor.h0100600000175000017500000000145107621424503013572 0ustar hsteohhsteoh/* * Simple X11 client-side cursor * Header file * * $Id: xcursor.h,v 1.2 2003/02/09 10:11:10 hsteoh Exp hsteoh $ */ #ifndef XCURSOR_H #define XCURSOR_H #include "xsprite.h" #include "xutil.h" class xcursor { xsprite_engine *eng; // [R] Drawable d; xflatsprite *cursor; // [R] xsavebuf bckgnd; int visible:1; int x,y; // last known cursor position void draw(); public: xcursor(xsprite_engine *eng, Drawable d, xflatsprite *cursor, int max_wd, int max_ht); ~xcursor(); void set_sprite(xflatsprite *cursor); // Turn cursor on/off. void on(); void off(); void move(int x, int y); // Access functions int is_on() { return visible; } int is_off() { return !visible; } int xcoor() { return x; } int ycoor() { return y; } }; #endif // XCURSOR_H atom4/x/xcursor.cc0100600000175000017500000000262707621601500013730 0ustar hsteohhsteoh/* * Simple X11 client-side cursor * Implementation file * * $Id: xcursor.cc,v 1.2 2003/02/10 01:41:05 hsteoh Exp hsteoh $ */ #include "exception.h" #include "xcursor.h" void xcursor::draw() { bckgnd.save(d, x-cursor->org_x(), y-cursor->org_y(), cursor->width(), cursor->height()); cursor->draw(d,x,y); } xcursor::xcursor(xsprite_engine *engine, Drawable drw, xflatsprite *curs, int max_wd, int max_ht) : eng(engine), d(drw), bckgnd(engine,d,max_wd,max_ht) { set_sprite(curs); visible=0; x=y=0; } xcursor::~xcursor() { if (visible) { bckgnd.restore(); } } void xcursor::set_sprite(xflatsprite *curs) { if (curs->width() > bckgnd.max_width() || curs->height() > bckgnd.max_height()) { throw exception("xcursor::set_sprite(): cursor sprite too big\n"); } else { cursor=curs; if (visible) { // redraw with new cursor image bckgnd.restore(); draw(); } } } void xcursor::on() { if (!visible) { visible=1; draw(); } } void xcursor::off() { if (visible) { bckgnd.restore(); } visible=0; } void xcursor::move(int new_x, int new_y) { if (visible) { if (new_x!=x || new_y!=y) { // only update if coors changed bckgnd.restore(); // erase from last position x = new_x; y = new_y; draw(); } } else { x = new_x; // still update internal coor anyway y = new_y; } } atom4/x/Conscript0100600000175000017500000000142407633531470013610 0ustar hsteohhsteoh#!/usr/bin/cons # # Atom-4 X11 client sub- build script. # # $Id: Conscript,v 1.4 2003/03/12 04:10:24 hsteoh Exp hsteoh $ # Import qw( CONS BINDIR DATADIR INCDIR LIBDIR ); # Products Install $CONS $LIBDIR, 'libxatom4.a'; Install $CONS $DATADIR, 'tritile12.xpm', 'blackball12.xpm', 'redball12.xpm', 'greenball12.xpm', 'blueball12.xpm', 'turqball12.xpm', 'purpleball12.xpm', 'yellowball12.xpm', 'whiteball12.xpm', 'wheel12-1.xpm', 'wheel12-2.xpm', 'wheel12-3.xpm', 'wheel12-4.xpm', 'wheel12-5.xpm', 'wheel12-6.xpm'; # Headers Install $CONS $INCDIR, 'xatom4.h', 'xcursor.h', 'xscoreboard.h', 'xsprite.h', 'xtext.h', 'xtriboard.h', 'xutil.h'; Library $CONS 'libxatom4.a', 'xatom4.cc', 'xcursor.cc', 'xscoreboard.cc', 'xsprite.cc', 'xtriboard.cc', 'xutil.cc'; atom4/x/wheel12w.xpm0100600000175000017500000001311407625161562014106 0ustar hsteohhsteoh/* XPM */ static char * wheel12w_xpm[] = { "72 66 43 1 12 12", " c None", ". c dark red", "X c #8b8b00", "o c #E02020", "O c #f0a0a0", "+ c #E0E020", "@ c #f0f0a0", "# c white", "$ c #e05050", "% c #e0e050", "& c #d00000", "* c #d0d000", "= c red", "- c yellow", "; c #8b008b", ": c #e0e0e0", "> c #008b00", ", c #E020E0", "< c #f0a0f0", "1 c #e8e8e8", "2 c #f8f8f8", "3 c #20E020", "4 c #a0f0a0", "5 c #e050e0", "6 c #f0f0f0", "7 c #50e050", "8 c black", "9 c #d000d0", "0 c #00d000", "q c magenta", "w c green", "e c dark blue", "r c #008b8b", "t c #2020E0", "y c #a0a0f0", "u c #20E0E0", "i c #a0f0f0", "p c #5050e0", "a c #50e0e0", "s c #0000d0", "d c #00d0d0", "f c blue", "g c cyan", " ...... XXXXXX ", " .......... XXXXXXXXXX ", " .oOO.......... X+@@XXXXXXXXXX ", " .O##$........... X@##%XXXXXXXXXXX ", " .O##O............. X@##@XXXXXXXXXXXXX ", " .O##$............... X@##%XXXXXXXXXXXXXXX ", " $##$................ %##%XXXXXXXXXXXXXXXX ", " .##O.................. X##@XXXXXXXXXXXXXXXXXX ", " .#o................... X#+XXXXXXXXXXXXXXXXXXX ", " ..o.....................XX+XXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ......................&.XXXXXXXXXXXXXXXXXXXXXX*X ", " .....................&&.XXXXXXXXXXXXXXXXXXXXX**X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " .................&=. XXXXXXXXXXXXXXXXX*-X ", " ................&==. XXXXXXXXXXXXXXXX*--X ", " ..............&==. XXXXXXXXXXXXXX*--X ", " ...........&===. XXXXXXXXXXX*---X ", " ;;;;;; ...=&&&&====.. :::::: XXX-****----XX >>>>>> ", " ;;;;;;;;;; ..======.. :::::::::: XX------XX >>>>>>>>>> ", " ;,<<;;;;;;;;;; ...... :122:::::::::: XXXXXX >344>>>>>>>>>> ", " ;<##5;;;;;;;;;;; :2##6::::::::::: >4##7>>>>>>>>>>> ", " ;<##<;;;;;;;;;;;;; :2##2::::::::::::: >4##4>>>>>>>>>>>>> ", " ;<##5;;;;;;;;;;;;;;; :2##6::::::::::::::: >4##7>>>>>>>>>>>>>>> ", " 5##5;;;;;;;;;;;;;;;; 6##6::::::8::::::::: 7##7>>>>>>>>>>>>>>>> ", " ;##<;;;;;;;;;;;;;;;;;; :##2::::::88:::::::::: >##4>>>>>>>>>>>>>>>>>> ", " ;#,;;;;;;;;;;;;;;;;;;; :#1:::::8888:::::::::: >#3>>>>>>>>>>>>>>>>>>> ", ";;,;;;;;;;;;;;;;;;;;;;;;::1::::::::88:::::::::::>>3>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::88:::::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::88:::::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::88:::::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;9;:::::::::::88:::::::::6:>>>>>>>>>>>>>>>>>>>>>>0>", ";;;;;;;;;;;;;;;;;;;;;99;:::::::::::88::::::::66:>>>>>>>>>>>>>>>>>>>>>00>", " ;;;;;;;;;;;;;;;;;;;9q; ::::::::::88:::::::6#: >>>>>>>>>>>>>>>>>>>0w> ", " ;;;;;;;;;;;;;;;;;;;9q; ::::::::888888:::::6#: >>>>>>>>>>>>>>>>>>>0w> ", " ;;;;;;;;;;;;;;;;;9q; :::::::::::::::::6#: >>>>>>>>>>>>>>>>>0w> ", " ;;;;;;;;;;;;;;;;9qq; ::::::::::::::::6##: >>>>>>>>>>>>>>>>0ww> ", " ;;;;;;;;;;;;;;9qq; ::::::::::::::6##: >>>>>>>>>>>>>>0ww> ", " ;;;;;;;;;;;9qqq; :::::::::::6###: >>>>>>>>>>>0www> ", " ;;;q9999qqqq;; eeeeee :::#6666####:: rrrrrr >>>w0000wwww>> ", " ;;qqqqqq;; eeeeeeeeee ::######:: rrrrrrrrrr >>wwwwww>> ", " ;;;;;; etyyeeeeeeeeee :::::: ruiirrrrrrrrrr >>>>>> ", " ey##peeeeeeeeeee ri##arrrrrrrrrrr ", " ey##yeeeeeeeeeeeee ri##irrrrrrrrrrrrr ", " ey##peeeeeeeeeeeeeee ri##arrrrrrrrrrrrrrr ", " p##peeeeeeeeeeeeeeee a##arrrrrrrrrrrrrrrr ", " e##yeeeeeeeeeeeeeeeeee r##irrrrrrrrrrrrrrrrrr ", " e#teeeeeeeeeeeeeeeeeee r#urrrrrrrrrrrrrrrrrrr ", " eeteeeeeeeeeeeeeeeeeeeeerrurrrrrrrrrrrrrrrrrrrrr ", " eeeeeeeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrrrrrrrrrr ", " eeeeeeeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrrrrrrrrrr ", " eeeeeeeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrrrrrrrrrr ", " eeeeeeeeeeeeeeeeeeeeeeserrrrrrrrrrrrrrrrrrrrrrdr ", " eeeeeeeeeeeeeeeeeeeeesserrrrrrrrrrrrrrrrrrrrrddr ", " eeeeeeeeeeeeeeeeeeesfe rrrrrrrrrrrrrrrrrrrdgr ", " eeeeeeeeeeeeeeeeeeesfe rrrrrrrrrrrrrrrrrrrdgr ", " eeeeeeeeeeeeeeeeesfe rrrrrrrrrrrrrrrrrdgr ", " eeeeeeeeeeeeeeeesffe rrrrrrrrrrrrrrrrdggr ", " eeeeeeeeeeeeeesffe rrrrrrrrrrrrrrdggr ", " eeeeeeeeeeesfffe rrrrrrrrrrrdgggr ", " eeefssssffffee rrrgddddggggrr ", " eeffffffee rrggggggrr ", " eeeeee rrrrrr "}; atom4/x/wheel12-3.xpm0100600000175000017500000001306107631270150014047 0ustar hsteohhsteoh/* XPM */ static char * wheel12_3_xpm[] = { "72 66 41 1 36 33", " c None", ". c dark red", "X c #8b8b00", "o c #E02020", "O c #f0a0a0", "+ c #E0E020", "@ c #f0f0a0", "# c white", "$ c #e05050", "% c #e0e050", "& c #d00000", "* c #d0d000", "= c red", "- c yellow", "; c #8b008b", ": c #e0e0e0", "> c #00ff00", ", c #E020E0", "< c #f0a0f0", "1 c #e8e8e8", "2 c #f8f8f8", "3 c #20E020", "4 c #a0f0a0", "5 c #e050e0", "6 c #f0f0f0", "7 c #50e050", "8 c black", "9 c #d000d0", "0 c magenta", "q c dark blue", "w c #008b8b", "e c #2020E0", "r c #a0a0f0", "t c #20E0E0", "y c #a0f0f0", "u c #5050e0", "i c #50e0e0", "p c #0000d0", "a c #00d0d0", "s c blue", "d c cyan", " ...... XXXXXX ", " .......... XXXXXXXXXX ", " .oOO.......... X+@@XXXXXXXXXX ", " .O##$........... X@##%XXXXXXXXXXX ", " .O##O............. X@##@XXXXXXXXXXXXX ", " .O##$............... X@##%XXXXXXXXXXXXXXX ", " $##$................ %##%XXXXXXXXXXXXXXXX ", " .##O.................. X##@XXXXXXXXXXXXXXXXXX ", " .#o................... X#+XXXXXXXXXXXXXXXXXXX ", " ..o.....................XX+XXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ......................&.XXXXXXXXXXXXXXXXXXXXXX*X ", " .....................&&.XXXXXXXXXXXXXXXXXXXXX**X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " .................&=. XXXXXXXXXXXXXXXXX*-X ", " ................&==. XXXXXXXXXXXXXXXX*--X ", " ..............&==. XXXXXXXXXXXXXX*--X ", " ...........&===. XXXXXXXXXXX*---X ", " ;;;;;; ...=&&&&====.. :::::: XXX-****----XX >>>>>> ", " ;;;;;;;;;; ..======.. :::::::::: XX------XX >>>>>>>>>> ", " ;,<<;;;;;;;;;; ...... :122:::::::::: XXXXXX >344>>>>>>>>>> ", " ;<##5;;;;;;;;;;; :2##6::::::::::: >4##7>>>>>>>>>>> ", " ;<##<;;;;;;;;;;;;; :2##2::::::::::::: >4##4>>>>>>>>>>>>> ", " ;<##5;;;;;;;;;;;;;;; :2##6::::::::::::::: >4##7>>>>>>>>>>>>>>> ", " 5##5;;;;;;;;;;;;;;;; 6##6::::::8::::::::: 7##7>>>>>>>>>>>>>>>> ", " ;##<;;;;;;;;;;;;;;;;;; :##2::::::88:::::::::: >##4>>>>>>>>>>>>>>>>>> ", " ;#,;;;;;;;;;;;;;;;;;;; :#1:::::8888:::::::::: >#3>>>>>>>>>>>>>>>>>>> ", ";;,;;;;;;;;;;;;;;;;;;;;;::1::::::::88:::::::::::>>3>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::88:::::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::88:::::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::88:::::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;9;:::::::::::88:::::::::6:>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;99;:::::::::::88::::::::66:>>>>>>>>>>>>>>>>>>>>>>>>", " ;;;;;;;;;;;;;;;;;;;90; ::::::::::88:::::::6#: >>>>>>>>>>>>>>>>>>>>>> ", " ;;;;;;;;;;;;;;;;;;;90; ::::::::888888:::::6#: >>>>>>>>>>>>>>>>>>>>>> ", " ;;;;;;;;;;;;;;;;;90; :::::::::::::::::6#: >>>>>>>>>>>>>>>>>>>> ", " ;;;;;;;;;;;;;;;;900; ::::::::::::::::6##: >>>>>>>>>>>>>>>>>>>> ", " ;;;;;;;;;;;;;;900; ::::::::::::::6##: >>>>>>>>>>>>>>>>>> ", " ;;;;;;;;;;;9000; :::::::::::6###: >>>>>>>>>>>>>>>> ", " ;;;099990000;; qqqqqq :::#6666####:: wwwwww >>>>>>>>>>>>>> ", " ;;000000;; qqqqqqqqqq ::######:: wwwwwwwwww >>>>>>>>>> ", " ;;;;;; qerrqqqqqqqqqq :::::: wtyywwwwwwwwww >>>>>> ", " qr##uqqqqqqqqqqq wy##iwwwwwwwwwww ", " qr##rqqqqqqqqqqqqq wy##ywwwwwwwwwwwww ", " qr##uqqqqqqqqqqqqqqq wy##iwwwwwwwwwwwwwww ", " u##uqqqqqqqqqqqqqqqq i##iwwwwwwwwwwwwwwww ", " q##rqqqqqqqqqqqqqqqqqq w##ywwwwwwwwwwwwwwwwww ", " q#eqqqqqqqqqqqqqqqqqqq w#twwwwwwwwwwwwwwwwwww ", " qqeqqqqqqqqqqqqqqqqqqqqqwwtwwwwwwwwwwwwwwwwwwwww ", " qqqqqqqqqqqqqqqqqqqqqqqqwwwwwwwwwwwwwwwwwwwwwwww ", " qqqqqqqqqqqqqqqqqqqqqqqqwwwwwwwwwwwwwwwwwwwwwwww ", " qqqqqqqqqqqqqqqqqqqqqqqqwwwwwwwwwwwwwwwwwwwwwwww ", " qqqqqqqqqqqqqqqqqqqqqqpqwwwwwwwwwwwwwwwwwwwwwwaw ", " qqqqqqqqqqqqqqqqqqqqqppqwwwwwwwwwwwwwwwwwwwwwaaw ", " qqqqqqqqqqqqqqqqqqqpsq wwwwwwwwwwwwwwwwwwwadw ", " qqqqqqqqqqqqqqqqqqqpsq wwwwwwwwwwwwwwwwwwwadw ", " qqqqqqqqqqqqqqqqqpsq wwwwwwwwwwwwwwwwwadw ", " qqqqqqqqqqqqqqqqpssq wwwwwwwwwwwwwwwwaddw ", " qqqqqqqqqqqqqqpssq wwwwwwwwwwwwwwaddw ", " qqqqqqqqqqqpsssq wwwwwwwwwwwadddw ", " qqqsppppssssqq wwwdaaaaddddww ", " qqssssssqq wwddddddww ", " qqqqqq wwwwww "}; atom4/x/wheel12-5.xpm0100600000175000017500000001305507631270171014057 0ustar hsteohhsteoh/* XPM */ static char * wheel12_5_xpm[] = { "72 66 41 1 36 33", " c None", ". c dark red", "X c #8b8b00", "o c #E02020", "O c #f0a0a0", "+ c #E0E020", "@ c #f0f0a0", "# c white", "$ c #e05050", "% c #e0e050", "& c #d00000", "* c #d0d000", "= c red", "- c yellow", "; c #8b008b", ": c #e0e0e0", "> c #008b00", ", c #E020E0", "< c #f0a0f0", "1 c #e8e8e8", "2 c #f8f8f8", "3 c #20E020", "4 c #a0f0a0", "5 c #e050e0", "6 c #f0f0f0", "7 c #50e050", "8 c black", "9 c #d000d0", "0 c #00d000", "q c magenta", "w c green", "e c blue", "r c #008b8b", "t c #2020E0", "y c #a0a0f0", "u c #20E0E0", "i c #a0f0f0", "p c #5050e0", "a c #50e0e0", "s c #00d0d0", "d c cyan", " ...... XXXXXX ", " .......... XXXXXXXXXX ", " .oOO.......... X+@@XXXXXXXXXX ", " .O##$........... X@##%XXXXXXXXXXX ", " .O##O............. X@##@XXXXXXXXXXXXX ", " .O##$............... X@##%XXXXXXXXXXXXXXX ", " $##$................ %##%XXXXXXXXXXXXXXXX ", " .##O.................. X##@XXXXXXXXXXXXXXXXXX ", " .#o................... X#+XXXXXXXXXXXXXXXXXXX ", " ..o.....................XX+XXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ......................&.XXXXXXXXXXXXXXXXXXXXXX*X ", " .....................&&.XXXXXXXXXXXXXXXXXXXXX**X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " .................&=. XXXXXXXXXXXXXXXXX*-X ", " ................&==. XXXXXXXXXXXXXXXX*--X ", " ..............&==. XXXXXXXXXXXXXX*--X ", " ...........&===. XXXXXXXXXXX*---X ", " ;;;;;; ...=&&&&====.. :::::: XXX-****----XX >>>>>> ", " ;;;;;;;;;; ..======.. :::::::::: XX------XX >>>>>>>>>> ", " ;,<<;;;;;;;;;; ...... :122:::::::::: XXXXXX >344>>>>>>>>>> ", " ;<##5;;;;;;;;;;; :2##6::::::::::: >4##7>>>>>>>>>>> ", " ;<##<;;;;;;;;;;;;; :2##2::::::::::::: >4##4>>>>>>>>>>>>> ", " ;<##5;;;;;;;;;;;;;;; :2##6::::::::::::::: >4##7>>>>>>>>>>>>>>> ", " 5##5;;;;;;;;;;;;;;;; 6##6::::::8::::::::: 7##7>>>>>>>>>>>>>>>> ", " ;##<;;;;;;;;;;;;;;;;;; :##2::::::88:::::::::: >##4>>>>>>>>>>>>>>>>>> ", " ;#,;;;;;;;;;;;;;;;;;;; :#1:::::8888:::::::::: >#3>>>>>>>>>>>>>>>>>>> ", ";;,;;;;;;;;;;;;;;;;;;;;;::1::::::::88:::::::::::>>3>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::88:::::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::88:::::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::88:::::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;9;:::::::::::88:::::::::6:>>>>>>>>>>>>>>>>>>>>>>0>", ";;;;;;;;;;;;;;;;;;;;;99;:::::::::::88::::::::66:>>>>>>>>>>>>>>>>>>>>>00>", " ;;;;;;;;;;;;;;;;;;;9q; ::::::::::88:::::::6#: >>>>>>>>>>>>>>>>>>>0w> ", " ;;;;;;;;;;;;;;;;;;;9q; ::::::::888888:::::6#: >>>>>>>>>>>>>>>>>>>0w> ", " ;;;;;;;;;;;;;;;;;9q; :::::::::::::::::6#: >>>>>>>>>>>>>>>>>0w> ", " ;;;;;;;;;;;;;;;;9qq; ::::::::::::::::6##: >>>>>>>>>>>>>>>>0ww> ", " ;;;;;;;;;;;;;;9qq; ::::::::::::::6##: >>>>>>>>>>>>>>0ww> ", " ;;;;;;;;;;;9qqq; :::::::::::6###: >>>>>>>>>>>0www> ", " ;;;q9999qqqq;; eeeeee :::#6666####:: rrrrrr >>>w0000wwww>> ", " ;;qqqqqq;; eeeeeeeeee ::######:: rrrrrrrrrr >>wwwwww>> ", " ;;;;;; etyyeeeeeeeeee :::::: ruiirrrrrrrrrr >>>>>> ", " ey##peeeeeeeeeee ri##arrrrrrrrrrr ", " ey##yeeeeeeeeeeeee ri##irrrrrrrrrrrrr ", " ey##peeeeeeeeeeeeeee ri##arrrrrrrrrrrrrrr ", " p##peeeeeeeeeeeeeeee a##arrrrrrrrrrrrrrrr ", " e##yeeeeeeeeeeeeeeeeee r##irrrrrrrrrrrrrrrrrr ", " e#teeeeeeeeeeeeeeeeeee r#urrrrrrrrrrrrrrrrrrr ", " eeteeeeeeeeeeeeeeeeeeeeerrurrrrrrrrrrrrrrrrrrrrr ", " eeeeeeeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrrrrrrrrrr ", " eeeeeeeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrrrrrrrrrr ", " eeeeeeeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrrrrrrrrrr ", " eeeeeeeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrrrrrrrrsr ", " eeeeeeeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrrrrrrrssr ", " eeeeeeeeeeeeeeeeeeeeee rrrrrrrrrrrrrrrrrrrsdr ", " eeeeeeeeeeeeeeeeeeeeee rrrrrrrrrrrrrrrrrrrsdr ", " eeeeeeeeeeeeeeeeeeee rrrrrrrrrrrrrrrrrsdr ", " eeeeeeeeeeeeeeeeeeee rrrrrrrrrrrrrrrrsddr ", " eeeeeeeeeeeeeeeeee rrrrrrrrrrrrrrsddr ", " eeeeeeeeeeeeeeee rrrrrrrrrrrsdddr ", " eeeeeeeeeeeeee rrrdssssddddrr ", " eeeeeeeeee rrddddddrr ", " eeeeee rrrrrr "}; atom4/x/wheel12k.xpm0100600000175000017500000001313307625160311014062 0ustar hsteohhsteoh/* XPM */ static char * wheel12k_xpm[] = { "72 66 44 1 12 12", " c None", ". c dark red", "X c #8b8b00", "o c #E02020", "O c #f0a0a0", "+ c #E0E020", "@ c #f0f0a0", "# c white", "$ c #e05050", "% c #e0e050", "& c #d00000", "* c #d0d000", "= c red", "- c yellow", "; c #8b008b", ": c #303030", "> c #008b00", ", c #E020E0", "< c #f0a0f0", "1 c #c0c0c0", "2 c #e0e0e0", "3 c #20E020", "4 c #a0f0a0", "5 c #e050e0", "6 c #a0a0a0", "7 c #50e050", "8 c #d000d0", "9 c #202020", "0 c #00d000", "q c magenta", "w c black", "e c green", "r c dark blue", "t c #008b8b", "y c #2020E0", "u c #a0a0f0", "i c #20E0E0", "p c #a0f0f0", "a c #5050e0", "s c #50e0e0", "d c #0000d0", "f c #00d0d0", "g c blue", "h c cyan", " ...... XXXXXX ", " .......... XXXXXXXXXX ", " .oOO.......... X+@@XXXXXXXXXX ", " .O##$........... X@##%XXXXXXXXXXX ", " .O##O............. X@##@XXXXXXXXXXXXX ", " .O##$............... X@##%XXXXXXXXXXXXXXX ", " $##$................ %##%XXXXXXXXXXXXXXXX ", " .##O.................. X##@XXXXXXXXXXXXXXXXXX ", " .#o................... X#+XXXXXXXXXXXXXXXXXXX ", " ..o.....................XX+XXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ......................&.XXXXXXXXXXXXXXXXXXXXXX*X ", " .....................&&.XXXXXXXXXXXXXXXXXXXXX**X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " .................&=. XXXXXXXXXXXXXXXXX*-X ", " ................&==. XXXXXXXXXXXXXXXX*--X ", " ..............&==. XXXXXXXXXXXXXX*--X ", " ...........&===. XXXXXXXXXXX*---X ", " ;;;;;; ...=&&&&====.. :::::: XXX-****----XX >>>>>> ", " ;;;;;;;;;; ..======.. :::::::::: XX------XX >>>>>>>>>> ", " ;,<<;;;;;;;;;; ...... :122:::::::::: XXXXXX >344>>>>>>>>>> ", " ;<##5;;;;;;;;;;; :2##6::::::::::: >4##7>>>>>>>>>>> ", " ;<##<;;;;;;;;;;;;; :2##2::::::::::::: >4##4>>>>>>>>>>>>> ", " ;<##5;;;;;;;;;;;;;;; :2##6::::::::::::::: >4##7>>>>>>>>>>>>>>> ", " 5##5;;;;;;;;;;;;;;;; 6##6::::####:::::::: 7##7>>>>>>>>>>>>>>>> ", " ;##<;;;;;;;;;;;;;;;;;; :##2::::##::##:::::::: >##4>>>>>>>>>>>>>>>>>> ", " ;#,;;;;;;;;;;;;;;;;;;; :#1::::1#::::##::::::: >#3>>>>>>>>>>>>>>>>>>> ", ";;,;;;;;;;;;;;;;;;;;;;;;::1:::::::::::##::::::::>>3>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;::::::::::::::##::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::::##:::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;::::::::::::##::::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;8;:::::::::::##:::::::::9:>>>>>>>>>>>>>>>>>>>>>>0>", ";;;;;;;;;;;;;;;;;;;;;88;::::::::::##:::::::::99:>>>>>>>>>>>>>>>>>>>>>00>", " ;;;;;;;;;;;;;;;;;;;8q; ::::::::##::::#::::9w: >>>>>>>>>>>>>>>>>>>0e> ", " ;;;;;;;;;;;;;;;;;;;8q; :::::::1#######::::9w: >>>>>>>>>>>>>>>>>>>0e> ", " ;;;;;;;;;;;;;;;;;8q; :::::::::::::::::9w: >>>>>>>>>>>>>>>>>0e> ", " ;;;;;;;;;;;;;;;;8qq; ::::::::::::::::9ww: >>>>>>>>>>>>>>>>0ee> ", " ;;;;;;;;;;;;;;8qq; ::::::::::::::9ww: >>>>>>>>>>>>>>0ee> ", " ;;;;;;;;;;;8qqq; :::::::::::9www: >>>>>>>>>>>0eee> ", " ;;;q8888qqqq;; rrrrrr :::w9999wwww:: tttttt >>>e0000eeee>> ", " ;;qqqqqq;; rrrrrrrrrr ::wwwwww:: tttttttttt >>eeeeee>> ", " ;;;;;; ryuurrrrrrrrrr :::::: tipptttttttttt >>>>>> ", " ru##arrrrrrrrrrr tp##sttttttttttt ", " ru##urrrrrrrrrrrrr tp##pttttttttttttt ", " ru##arrrrrrrrrrrrrrr tp##sttttttttttttttt ", " a##arrrrrrrrrrrrrrrr s##stttttttttttttttt ", " r##urrrrrrrrrrrrrrrrrr t##ptttttttttttttttttt ", " r#yrrrrrrrrrrrrrrrrrrr t#ittttttttttttttttttt ", " rryrrrrrrrrrrrrrrrrrrrrrttittttttttttttttttttttt ", " rrrrrrrrrrrrrrrrrrrrrrrrtttttttttttttttttttttttt ", " rrrrrrrrrrrrrrrrrrrrrrrrtttttttttttttttttttttttt ", " rrrrrrrrrrrrrrrrrrrrrrrrtttttttttttttttttttttttt ", " rrrrrrrrrrrrrrrrrrrrrrdrttttttttttttttttttttttft ", " rrrrrrrrrrrrrrrrrrrrrddrtttttttttttttttttttttfft ", " rrrrrrrrrrrrrrrrrrrdgr tttttttttttttttttttfht ", " rrrrrrrrrrrrrrrrrrrdgr tttttttttttttttttttfht ", " rrrrrrrrrrrrrrrrrdgr tttttttttttttttttfht ", " rrrrrrrrrrrrrrrrdggr ttttttttttttttttfhht ", " rrrrrrrrrrrrrrdggr ttttttttttttttfhht ", " rrrrrrrrrrrdgggr tttttttttttfhhht ", " rrrgddddggggrr ttthffffhhhhtt ", " rrggggggrr tthhhhhhtt ", " rrrrrr tttttt "}; atom4/x/wheel12-2.xpm0100600000175000017500000001307607631270125014056 0ustar hsteohhsteoh/* XPM */ static char * wheel12_2_xpm[] = { "72 66 42 1 36 33", " c None", ". c dark red", "X c yellow", "o c #E02020", "O c #f0a0a0", "+ c #E0E020", "@ c #f0f0a0", "# c white", "$ c #e05050", "% c #e0e050", "& c #d00000", "* c red", "= c #8b008b", "- c #303030", "; c #008b00", ": c #E020E0", "> c #f0a0f0", ", c #c0c0c0", "< c #e0e0e0", "1 c #20E020", "2 c #a0f0a0", "3 c #e050e0", "4 c #a0a0a0", "5 c #50e050", "6 c #d000d0", "7 c #202020", "8 c #00d000", "9 c magenta", "0 c black", "q c green", "w c dark blue", "e c #008b8b", "r c #2020E0", "t c #a0a0f0", "y c #20E0E0", "u c #a0f0f0", "i c #5050e0", "p c #50e0e0", "a c #0000d0", "s c #00d0d0", "d c blue", "f c cyan", " ...... XXXXXX ", " .......... XXXXXXXXXX ", " .oOO.......... X+@@XXXXXXXXXX ", " .O##$........... X@##%XXXXXXXXXXX ", " .O##O............. X@##@XXXXXXXXXXXXX ", " .O##$............... X@##%XXXXXXXXXXXXXXX ", " $##$................ %##%XXXXXXXXXXXXXXXX ", " .##O.................. X##@XXXXXXXXXXXXXXXXXX ", " .#o................... X#+XXXXXXXXXXXXXXXXXXX ", " ..o.....................XX+XXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ......................&.XXXXXXXXXXXXXXXXXXXXXXXX ", " .....................&&.XXXXXXXXXXXXXXXXXXXXXXXX ", " ...................&*. XXXXXXXXXXXXXXXXXXXXXX ", " ...................&*. XXXXXXXXXXXXXXXXXXXXXX ", " .................&*. XXXXXXXXXXXXXXXXXXXX ", " ................&**. XXXXXXXXXXXXXXXXXXXX ", " ..............&**. XXXXXXXXXXXXXXXXXX ", " ...........&***. XXXXXXXXXXXXXXXX ", " ====== ...*&&&&****.. ------ XXXXXXXXXXXXXX ;;;;;; ", " ========== ..******.. ---------- XXXXXXXXXX ;;;;;;;;;; ", " =:>>========== ...... -,<<---------- XXXXXX ;122;;;;;;;;;; ", " =>##3=========== -<##4----------- ;2##5;;;;;;;;;;; ", " =>##>============= -<##<------------- ;2##2;;;;;;;;;;;;; ", " =>##3=============== -<##4--------------- ;2##5;;;;;;;;;;;;;;; ", " 3##3================ 4##4----####-------- 5##5;;;;;;;;;;;;;;;; ", " =##>================== -##<----##--##-------- ;##2;;;;;;;;;;;;;;;;;; ", " =#:=================== -#,----,#----##------- ;#1;;;;;;;;;;;;;;;;;;; ", "==:=====================--,-----------##--------;;1;;;;;;;;;;;;;;;;;;;;;", "========================--------------##--------;;;;;;;;;;;;;;;;;;;;;;;;", "========================-------------##---------;;;;;;;;;;;;;;;;;;;;;;;;", "========================------------##----------;;;;;;;;;;;;;;;;;;;;;;;;", "======================6=-----------##---------7-;;;;;;;;;;;;;;;;;;;;;;8;", "=====================66=----------##---------77-;;;;;;;;;;;;;;;;;;;;;88;", " ===================69= --------##----#----70- ;;;;;;;;;;;;;;;;;;;8q; ", " ===================69= -------,#######----70- ;;;;;;;;;;;;;;;;;;;8q; ", " =================69= -----------------70- ;;;;;;;;;;;;;;;;;8q; ", " ================699= ----------------700- ;;;;;;;;;;;;;;;;8qq; ", " ==============699= --------------700- ;;;;;;;;;;;;;;8qq; ", " ===========6999= -----------7000- ;;;;;;;;;;;8qqq; ", " ===966669999== wwwwww ---077770000-- eeeeee ;;;q8888qqqq;; ", " ==999999== wwwwwwwwww --000000-- eeeeeeeeee ;;qqqqqq;; ", " ====== wrttwwwwwwwwww ------ eyuueeeeeeeeee ;;;;;; ", " wt##iwwwwwwwwwww eu##peeeeeeeeeee ", " wt##twwwwwwwwwwwww eu##ueeeeeeeeeeeee ", " wt##iwwwwwwwwwwwwwww eu##peeeeeeeeeeeeeee ", " i##iwwwwwwwwwwwwwwww p##peeeeeeeeeeeeeeee ", " w##twwwwwwwwwwwwwwwwww e##ueeeeeeeeeeeeeeeeee ", " w#rwwwwwwwwwwwwwwwwwww e#yeeeeeeeeeeeeeeeeeee ", " wwrwwwwwwwwwwwwwwwwwwwwweeyeeeeeeeeeeeeeeeeeeeee ", " wwwwwwwwwwwwwwwwwwwwwwwweeeeeeeeeeeeeeeeeeeeeeee ", " wwwwwwwwwwwwwwwwwwwwwwwweeeeeeeeeeeeeeeeeeeeeeee ", " wwwwwwwwwwwwwwwwwwwwwwwweeeeeeeeeeeeeeeeeeeeeeee ", " wwwwwwwwwwwwwwwwwwwwwwaweeeeeeeeeeeeeeeeeeeeeese ", " wwwwwwwwwwwwwwwwwwwwwaaweeeeeeeeeeeeeeeeeeeeesse ", " wwwwwwwwwwwwwwwwwwwadw eeeeeeeeeeeeeeeeeeesfe ", " wwwwwwwwwwwwwwwwwwwadw eeeeeeeeeeeeeeeeeeesfe ", " wwwwwwwwwwwwwwwwwadw eeeeeeeeeeeeeeeeesfe ", " wwwwwwwwwwwwwwwwaddw eeeeeeeeeeeeeeeesffe ", " wwwwwwwwwwwwwwaddw eeeeeeeeeeeeeesffe ", " wwwwwwwwwwwadddw eeeeeeeeeeesfffe ", " wwwdaaaaddddww eeefssssffffee ", " wwddddddww eeffffffee ", " wwwwww eeeeee "}; atom4/x/wheel12-4.xpm0100600000175000017500000001307607631270160014057 0ustar hsteohhsteoh/* XPM */ static char * wheel12_4_xpm[] = { "72 66 42 1 36 33", " c None", ". c dark red", "X c #8b8b00", "o c #E02020", "O c #f0a0a0", "+ c #E0E020", "@ c #f0f0a0", "# c white", "$ c #e05050", "% c #e0e050", "& c #d00000", "* c #d0d000", "= c red", "- c yellow", "; c #8b008b", ": c #303030", "> c #008b00", ", c #E020E0", "< c #f0a0f0", "1 c #c0c0c0", "2 c #e0e0e0", "3 c #20E020", "4 c #a0f0a0", "5 c #e050e0", "6 c #a0a0a0", "7 c #50e050", "8 c #d000d0", "9 c #202020", "0 c #00d000", "q c magenta", "w c black", "e c green", "r c dark blue", "t c cyan", "y c #2020E0", "u c #a0a0f0", "i c #20E0E0", "p c #a0f0f0", "a c #5050e0", "s c #50e0e0", "d c #0000d0", "f c blue", " ...... XXXXXX ", " .......... XXXXXXXXXX ", " .oOO.......... X+@@XXXXXXXXXX ", " .O##$........... X@##%XXXXXXXXXXX ", " .O##O............. X@##@XXXXXXXXXXXXX ", " .O##$............... X@##%XXXXXXXXXXXXXXX ", " $##$................ %##%XXXXXXXXXXXXXXXX ", " .##O.................. X##@XXXXXXXXXXXXXXXXXX ", " .#o................... X#+XXXXXXXXXXXXXXXXXXX ", " ..o.....................XX+XXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ......................&.XXXXXXXXXXXXXXXXXXXXXX*X ", " .....................&&.XXXXXXXXXXXXXXXXXXXXX**X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " .................&=. XXXXXXXXXXXXXXXXX*-X ", " ................&==. XXXXXXXXXXXXXXXX*--X ", " ..............&==. XXXXXXXXXXXXXX*--X ", " ...........&===. XXXXXXXXXXX*---X ", " ;;;;;; ...=&&&&====.. :::::: XXX-****----XX >>>>>> ", " ;;;;;;;;;; ..======.. :::::::::: XX------XX >>>>>>>>>> ", " ;,<<;;;;;;;;;; ...... :122:::::::::: XXXXXX >344>>>>>>>>>> ", " ;<##5;;;;;;;;;;; :2##6::::::::::: >4##7>>>>>>>>>>> ", " ;<##<;;;;;;;;;;;;; :2##2::::::::::::: >4##4>>>>>>>>>>>>> ", " ;<##5;;;;;;;;;;;;;;; :2##6::::::::::::::: >4##7>>>>>>>>>>>>>>> ", " 5##5;;;;;;;;;;;;;;;; 6##6::::####:::::::: 7##7>>>>>>>>>>>>>>>> ", " ;##<;;;;;;;;;;;;;;;;;; :##2::::##::##:::::::: >##4>>>>>>>>>>>>>>>>>> ", " ;#,;;;;;;;;;;;;;;;;;;; :#1::::1#::::##::::::: >#3>>>>>>>>>>>>>>>>>>> ", ";;,;;;;;;;;;;;;;;;;;;;;;::1:::::::::::##::::::::>>3>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;::::::::::::::##::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::::##:::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;::::::::::::##::::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;8;:::::::::::##:::::::::9:>>>>>>>>>>>>>>>>>>>>>>0>", ";;;;;;;;;;;;;;;;;;;;;88;::::::::::##:::::::::99:>>>>>>>>>>>>>>>>>>>>>00>", " ;;;;;;;;;;;;;;;;;;;8q; ::::::::##::::#::::9w: >>>>>>>>>>>>>>>>>>>0e> ", " ;;;;;;;;;;;;;;;;;;;8q; :::::::1#######::::9w: >>>>>>>>>>>>>>>>>>>0e> ", " ;;;;;;;;;;;;;;;;;8q; :::::::::::::::::9w: >>>>>>>>>>>>>>>>>0e> ", " ;;;;;;;;;;;;;;;;8qq; ::::::::::::::::9ww: >>>>>>>>>>>>>>>>0ee> ", " ;;;;;;;;;;;;;;8qq; ::::::::::::::9ww: >>>>>>>>>>>>>>0ee> ", " ;;;;;;;;;;;8qqq; :::::::::::9www: >>>>>>>>>>>0eee> ", " ;;;q8888qqqq;; rrrrrr :::w9999wwww:: tttttt >>>e0000eeee>> ", " ;;qqqqqq;; rrrrrrrrrr ::wwwwww:: tttttttttt >>eeeeee>> ", " ;;;;;; ryuurrrrrrrrrr :::::: tipptttttttttt >>>>>> ", " ru##arrrrrrrrrrr tp##sttttttttttt ", " ru##urrrrrrrrrrrrr tp##pttttttttttttt ", " ru##arrrrrrrrrrrrrrr tp##sttttttttttttttt ", " a##arrrrrrrrrrrrrrrr s##stttttttttttttttt ", " r##urrrrrrrrrrrrrrrrrr t##ptttttttttttttttttt ", " r#yrrrrrrrrrrrrrrrrrrr t#ittttttttttttttttttt ", " rryrrrrrrrrrrrrrrrrrrrrrttittttttttttttttttttttt ", " rrrrrrrrrrrrrrrrrrrrrrrrtttttttttttttttttttttttt ", " rrrrrrrrrrrrrrrrrrrrrrrrtttttttttttttttttttttttt ", " rrrrrrrrrrrrrrrrrrrrrrrrtttttttttttttttttttttttt ", " rrrrrrrrrrrrrrrrrrrrrrdrtttttttttttttttttttttttt ", " rrrrrrrrrrrrrrrrrrrrrddrtttttttttttttttttttttttt ", " rrrrrrrrrrrrrrrrrrrdfr tttttttttttttttttttttt ", " rrrrrrrrrrrrrrrrrrrdfr tttttttttttttttttttttt ", " rrrrrrrrrrrrrrrrrdfr tttttttttttttttttttt ", " rrrrrrrrrrrrrrrrdffr tttttttttttttttttttt ", " rrrrrrrrrrrrrrdffr tttttttttttttttttt ", " rrrrrrrrrrrdfffr tttttttttttttttt ", " rrrfddddffffrr tttttttttttttt ", " rrffffffrr tttttttttt ", " rrrrrr tttttt "}; atom4/x/wheel12-6.xpm0100600000175000017500000001307607631270200014054 0ustar hsteohhsteoh/* XPM */ static char * wheel12_6_xpm[] = { "72 66 42 1 36 33", " c None", ". c dark red", "X c #8b8b00", "o c #E02020", "O c #f0a0a0", "+ c #E0E020", "@ c #f0f0a0", "# c white", "$ c #e05050", "% c #e0e050", "& c #d00000", "* c #d0d000", "= c red", "- c yellow", "; c magenta", ": c #303030", "> c #008b00", ", c #E020E0", "< c #f0a0f0", "1 c #c0c0c0", "2 c #e0e0e0", "3 c #20E020", "4 c #a0f0a0", "5 c #e050e0", "6 c #a0a0a0", "7 c #50e050", "8 c #202020", "9 c #00d000", "0 c black", "q c green", "w c dark blue", "e c #008b8b", "r c #2020E0", "t c #a0a0f0", "y c #20E0E0", "u c #a0f0f0", "i c #5050e0", "p c #50e0e0", "a c #0000d0", "s c #00d0d0", "d c blue", "f c cyan", " ...... XXXXXX ", " .......... XXXXXXXXXX ", " .oOO.......... X+@@XXXXXXXXXX ", " .O##$........... X@##%XXXXXXXXXXX ", " .O##O............. X@##@XXXXXXXXXXXXX ", " .O##$............... X@##%XXXXXXXXXXXXXXX ", " $##$................ %##%XXXXXXXXXXXXXXXX ", " .##O.................. X##@XXXXXXXXXXXXXXXXXX ", " .#o................... X#+XXXXXXXXXXXXXXXXXXX ", " ..o.....................XX+XXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ........................XXXXXXXXXXXXXXXXXXXXXXXX ", " ......................&.XXXXXXXXXXXXXXXXXXXXXX*X ", " .....................&&.XXXXXXXXXXXXXXXXXXXXX**X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " ...................&=. XXXXXXXXXXXXXXXXXXX*-X ", " .................&=. XXXXXXXXXXXXXXXXX*-X ", " ................&==. XXXXXXXXXXXXXXXX*--X ", " ..............&==. XXXXXXXXXXXXXX*--X ", " ...........&===. XXXXXXXXXXX*---X ", " ;;;;;; ...=&&&&====.. :::::: XXX-****----XX >>>>>> ", " ;;;;;;;;;; ..======.. :::::::::: XX------XX >>>>>>>>>> ", " ;,<<;;;;;;;;;; ...... :122:::::::::: XXXXXX >344>>>>>>>>>> ", " ;<##5;;;;;;;;;;; :2##6::::::::::: >4##7>>>>>>>>>>> ", " ;<##<;;;;;;;;;;;;; :2##2::::::::::::: >4##4>>>>>>>>>>>>> ", " ;<##5;;;;;;;;;;;;;;; :2##6::::::::::::::: >4##7>>>>>>>>>>>>>>> ", " 5##5;;;;;;;;;;;;;;;; 6##6::::####:::::::: 7##7>>>>>>>>>>>>>>>> ", " ;##<;;;;;;;;;;;;;;;;;; :##2::::##::##:::::::: >##4>>>>>>>>>>>>>>>>>> ", " ;#,;;;;;;;;;;;;;;;;;;; :#1::::1#::::##::::::: >#3>>>>>>>>>>>>>>>>>>> ", ";;,;;;;;;;;;;;;;;;;;;;;;::1:::::::::::##::::::::>>3>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;::::::::::::::##::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::::##:::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;::::::::::::##::::::::::>>>>>>>>>>>>>>>>>>>>>>>>", ";;;;;;;;;;;;;;;;;;;;;;;;:::::::::::##:::::::::8:>>>>>>>>>>>>>>>>>>>>>>9>", ";;;;;;;;;;;;;;;;;;;;;;;;::::::::::##:::::::::88:>>>>>>>>>>>>>>>>>>>>>99>", " ;;;;;;;;;;;;;;;;;;;;;; ::::::::##::::#::::80: >>>>>>>>>>>>>>>>>>>9q> ", " ;;;;;;;;;;;;;;;;;;;;;; :::::::1#######::::80: >>>>>>>>>>>>>>>>>>>9q> ", " ;;;;;;;;;;;;;;;;;;;; :::::::::::::::::80: >>>>>>>>>>>>>>>>>9q> ", " ;;;;;;;;;;;;;;;;;;;; ::::::::::::::::800: >>>>>>>>>>>>>>>>9qq> ", " ;;;;;;;;;;;;;;;;;; ::::::::::::::800: >>>>>>>>>>>>>>9qq> ", " ;;;;;;;;;;;;;;;; :::::::::::8000: >>>>>>>>>>>9qqq> ", " ;;;;;;;;;;;;;; wwwwww :::088880000:: eeeeee >>>q9999qqqq>> ", " ;;;;;;;;;; wwwwwwwwww ::000000:: eeeeeeeeee >>qqqqqq>> ", " ;;;;;; wrttwwwwwwwwww :::::: eyuueeeeeeeeee >>>>>> ", " wt##iwwwwwwwwwww eu##peeeeeeeeeee ", " wt##twwwwwwwwwwwww eu##ueeeeeeeeeeeee ", " wt##iwwwwwwwwwwwwwww eu##peeeeeeeeeeeeeee ", " i##iwwwwwwwwwwwwwwww p##peeeeeeeeeeeeeeee ", " w##twwwwwwwwwwwwwwwwww e##ueeeeeeeeeeeeeeeeee ", " w#rwwwwwwwwwwwwwwwwwww e#yeeeeeeeeeeeeeeeeeee ", " wwrwwwwwwwwwwwwwwwwwwwwweeyeeeeeeeeeeeeeeeeeeeee ", " wwwwwwwwwwwwwwwwwwwwwwwweeeeeeeeeeeeeeeeeeeeeeee ", " wwwwwwwwwwwwwwwwwwwwwwwweeeeeeeeeeeeeeeeeeeeeeee ", " wwwwwwwwwwwwwwwwwwwwwwwweeeeeeeeeeeeeeeeeeeeeeee ", " wwwwwwwwwwwwwwwwwwwwwwaweeeeeeeeeeeeeeeeeeeeeese ", " wwwwwwwwwwwwwwwwwwwwwaaweeeeeeeeeeeeeeeeeeeeesse ", " wwwwwwwwwwwwwwwwwwwadw eeeeeeeeeeeeeeeeeeesfe ", " wwwwwwwwwwwwwwwwwwwadw eeeeeeeeeeeeeeeeeeesfe ", " wwwwwwwwwwwwwwwwwadw eeeeeeeeeeeeeeeeesfe ", " wwwwwwwwwwwwwwwwaddw eeeeeeeeeeeeeeeesffe ", " wwwwwwwwwwwwwwaddw eeeeeeeeeeeeeesffe ", " wwwwwwwwwwwadddw eeeeeeeeeeesfffe ", " wwwdaaaaddddww eeefssssffffee ", " wwddddddww eeffffffee ", " wwwwww eeeeee "}; atom4/x/xgraphics.h0100600000175000017500000000053307631245533014062 0ustar hsteohhsteoh/* * Simple X11 graphics resource manager * Header file * * $Id$ */ #ifndef XGRAPHICS_H #define XGRAPHICS_H #include "xutil.h" typedef int image_id; class xgraphics { xconnection *conn; // [R] public: xgraphics(xconnection *conn, resource_manager *man); ~xgraphics(); Pixmap get_image(image_id id); }; #endif // XGRAPHICS_H atom4/x/resource.def0100600000175000017500000000060207631257456014234 0ustar hsteohhsteoh# # XAtom4 resource bindings # # $Id$ # path imagepath /home/hsteoh/prog/atom4/x xpm tritile_xpm tritile12.xpm xpm blackball_xpm blackball12.xpm xpm redball_xpm redball12.xpm xpm greenball_xpm greenball12.xpm xpm blueball_xpm blueball12.xpm xpm turqball_xpm turqball12.xpm xpm purpleball_xpm purpleball12.xpm xpm yellowball_xpm yellowball12.xpm xpm whiteball_xpm whiteball12.xpm atom4/x/resource.types0100600000175000017500000000035507631261447014642 0ustar hsteohhsteoh# # XAtom4 Resource Types # # $Id$ # resource path { } resource GC { } resource xpm { requires xconnection* internal_typedef struct ximage { XImage img,mask; } internal_type ximage load { # ... } } resource sprite { } atom4/x/context.h0100600000175000017500000000035007644105004013543 0ustar hsteohhsteoh/* * Atom-4 context class * For sharing common resources * Header file * * $Id$ */ #ifndef CONTEXT_H #define CONTEXT_H class atom4context { public: atom4context(); ~atom4context(); GC copygc; }; #endif // CONTEXT_H atom4/engine/triboard.cc0100600000175000017500000000537007645303352015037 0ustar hsteohhsteoh/* * Generic triangular board representation * Implementation file * --------------------------------------------------------------------------- * $Id: triboard.cc,v 1.6 2003/03/17 16:48:23 hsteoh Exp hsteoh $ */ #include #include "triboard.h" void triboard::copy(triboard &b) { wd=b.wd, ht=b.ht; ox=b.ox, oy=b.oy; board = new celltype[wd*ht]; for (int y=0; y0 && height>0); wd = width; ht = height; // Allocate board board = new celltype[wd*ht]; assert(board); clear(); // start with empty board } triboard &triboard::operator= (triboard &b) { delete [] board; copy(b); return *this; } triboard::~triboard() { delete [] board; } celltype triboard::getcell(int x, int y) { x += ox; // convert to absolute board coors y += oy; if (x<0 || x>=wd || y<0 || y>=ht) { return BAD_CELL; // no such cell } else { return board[y*wd + x]; } } void triboard::setcell(int x, int y, celltype cell) { x += ox; y += oy; if (x>=0 && x=0 && y=wd || y<0 || y>=ht || dir<0 || dir>=6) return 0; switch(dir) { case 0: nx = x-1 + (y%2); ny = y-1; break; case 1: nx = x + (y%2); ny = y-1; break; case 2: nx = x+1; ny = y; break; case 3: nx = x + (y%2); ny = y+1; break; case 4: nx = x-1 + (y%2); ny = y+1; break; case 5: nx = x-1; ny = y; break; default: return 0; } // Make sure resulting coors are actually legal return (nx>=0 && nx=0 && ny &changes) { elistiter it; for (it=notifylist.headp(); it; it++) { (*it)->notify_move(this, player, changes); } } void atom4::notify_clear() { elistiter it; for (it=notifylist.headp(); it; it++) { (*it)->notify_clear(this); } } atom4::~atom4() { // needed 'cos this is a virtual dtor to allow proper cleanup of derived // objects. } void atom4::add_notifier(atom4notifier *callback) { notifylist.append(callback); } void atom4::remove_notifier(atom4notifier *callback) { elistiter it, prev; prev.invalidate(); for (it=notifylist.headp(); it; it++) { if (*it == callback) { notifylist.remove(prev); } prev=it; } } /* * class atom4local */ // TESTING: sequence of colors each player gets to play // (Interleaved between two players) celltype atom4local::colorseq[COLORSEQ_LEN] = { 'b', 'd', 'c', 'g', 'e', 'f' }; // Returns 0 for normal operations; 1 if a winning position is detected. int atom4local::splash(int bx, int by, color4 actor, elist &changes) { board->compute_splash(bx, by, actor, changes); board->applychanges(changes, NULL); return board->check_win(changes, actor.propagator(), WIN_ROW); } void atom4local::reset_scores() { int i; for (i=0; iclear(); x = board->width()/2 - 1; y = board->height()/2 - 1; // Starting color cur_color = (cur_round-1) % COLORSEQ_LEN; // Complementary setting board->setcell(x,y, colorseq[(cur_color+2) % COLORSEQ_LEN]); board->setcell(x+1,y, colorseq[(cur_color+5) % COLORSEQ_LEN]); // Pyramid setting // board->setcell(x,y, 'a'); // board->ngbr_coor(x,y,2,x,y); board->setcell(x,y, 'b'); // board->ngbr_coor(x,y,4,x,y); board->setcell(x,y, 'c'); // // Diamond configuration // board->setcell(x,y, plchar(hplayer)); // board->ngbr_coor(x,y,1,x,y); board->setcell(x,y, plchar(vplayer)); // board->ngbr_coor(x,y,3,x,y); board->setcell(x,y, plchar(hplayer)); // board->ngbr_coor(x,y,4,x,y); board->setcell(x,y, plchar(vplayer)); } atom4local::atom4local(unsigned int width, unsigned int height) { board = new board4(width,height); reset_scores(); reset_board(); } atom4local::~atom4local() { delete board; } void atom4local::reset() { reset_scores(); reset_board(); notify_clear(); } atom4::mode_t atom4local::game_mode() { return atom4::DUAL; } int atom4local::local_playernum() { return cur_player; } int atom4local::is_local_turn() { // We're running in DUAL mode, so it's always the local player's turn until // the game is over. return win_player==-1; } int atom4local::current_player() { return (win_player==-1) ? cur_player : -1; } celltype atom4local::current_tile() { return colorseq[cur_color]; } int atom4local::move(int player, int x, int y) { if (!valid_player(player)) return 0; // sanity check if (win_player!=-1 || player!=cur_player || !check_legal(x,y)) { return 0; // illegal move } else { elist changes; int move_player = cur_player; if (splash(x,y,colorseq[cur_color], changes)) { win_player = player; scores[player-1]++; } else { if (board->is_full()) { win_player = STALEMATE; // no more moves left } else { // Next player's turn cur_player = next_player(cur_player); cur_color = next_color(cur_color); } } notify_move(move_player, changes); return 1; } } // Check if given move is legal. int atom4local::check_legal(int x, int y) { return board->check_legal(x,y); } int atom4local::round_over() { return (win_player!=-1); } int atom4local::winner() { return win_player; } int atom4local::current_round() { return cur_round; } int atom4local::newround() { if (win_player != -1) { win_player=-1; // start next round cur_round++; start_player=next_player(start_player); // other player gets to start cur_player=start_player; reset_board(); // setup board for next round notify_clear(); return 1; } else { return 0; } } int atom4local::score(int player) { return (valid_player(player)) ? scores[player-1] : 0; } board4 *atom4local::get_board() { return board; } unsigned int atom4local::board_width() { return board->width(); } unsigned int atom4local::board_height() { return board->height(); } atom4/engine/game.h0100600000175000017500000001225507646650506014013 0ustar hsteohhsteoh/* * Atom-4 game engine * Header file * --------------------------------------------------------------------------- * $Id: game.h,v 1.20 2003/04/15 00:24:21 hsteoh Exp hsteoh $ * * DESIGN * * This game engine is supposed to be UI-driven; so it is basically designed * for an event-driven interface. The UI, whether the testing ncurses-based * UI or the anticipated X11 UI, will be sending events to class atom4 and * checking its state to know what to do next, etc.. Basically, this class * implements the game rules. */ #ifndef GAME_H #define GAME_H #include // MUST be prog/lib version! #include "board4.h" #include "color4.h" #include "triboard.h" #define STALEMATE 3 // returned by atom4::winner() #define NUM_COLORS 8 #define NUM_PLAYERS 2 #define WIN_ROW 4 // number of pieces in a row to win // Internal stuff #define COLORSEQ_LEN 6 // Callback class for handling allowing async notification of game state // changes class atom4; // forward decl class atom4notifier { public: // Notify of all game state changes virtual void notify_move(atom4 *src, int plr, elist &chg)=0; virtual void notify_clear(atom4 *src)=0; }; // Abstract base class class atom4 { elist notifylist; protected: // Send notification to all registered notifiers. Derived classes must // call this every time a game state change is detected. virtual void notify_move(int player, elist &changes); virtual void notify_clear(); public: enum mode_t { DUAL, // both players connected to interface PEER, // one player connected to interface WATCH // not actively participating }; virtual ~atom4(); virtual void reset()=0; // restart game virtual mode_t game_mode()=0; // return current mode virtual int local_playernum()=0; // assigned player number if in PEER // mode; current_player() if DUAL mode, // -1 if WATCH mode. virtual int is_local_turn()=0; void add_notifier(atom4notifier *callback); // [R] void remove_notifier(atom4notifier *callback); // [R] virtual int current_player()=0; virtual celltype current_tile()=0; // Returns false if move is illegal (not player's turn, bad position, game // already over, etc.), true otherwise. virtual int move(int player, int x, int y)=0; // Checks if move is legal (note: this does NOT guarantee a subsequent // call to move() will be successful, since this only checks the board // not the current state of the game) virtual int check_legal(int x, int y)=0; virtual int round_over()=0; virtual int winner()=0; // Multi-round support virtual int current_round()=0; virtual int newround()=0; virtual int score(int player)=0; // FIXME: what we REALLY want is to have a way to register an update // callback with class triboard, that will get called every time a cell // changes. (This is the REAL answer to the whole mess in the X11 refresh() // code right now...) virtual board4 *get_board()=0; virtual unsigned int board_width()=0; virtual unsigned int board_height()=0; }; class atom4local : public atom4 { board4 *board; // [O] game board int win_player; // player who won the game; if not -1, // game is over. int cur_player; // player to make the next move int cur_color; // which color is being played static celltype colorseq[COLORSEQ_LEN]; // Multi-round stuff int cur_round; // current round int start_player; // player to move first int scores[NUM_PLAYERS]; // Internal convenience stuff inline char plchar(int player) { return 'a' + player-1; } inline int next_player(int player) { return player%NUM_PLAYERS + 1; } inline int valid_player(int pl) { return pl>0 && pl<=NUM_PLAYERS; } inline int next_color(int color) { return (color+1)%COLORSEQ_LEN; } int splash(int bx, int by, color4 actor, elist &changes); void reset_scores(); void reset_board(); public: atom4local(unsigned int width, unsigned int height); virtual ~atom4local(); virtual void reset(); // restart game virtual mode_t game_mode(); // return current mode virtual int local_playernum(); virtual int is_local_turn(); // For UI to know who's supposed to move next. UI should not need to keep // track of this independently anyway. int current_player(); // Current marble (which color) being played celltype current_tile(); // Returns false if move is illegal (not player's turn, bad position, game // already over, etc.), true otherwise. int move(int player, int x, int y); // Checks if move is legal (note: this does NOT guarantee a subsequent // call to move() will be successful, since this only checks the board // not the current state of the game) int check_legal(int x, int y); int round_over(); int winner(); // Multi-round support int current_round(); virtual int newround(); int score(int player); // FIXME: what we REALLY want is to have a way to register an update // callback with class triboard, that will get called every time a cell // changes. (This is the REAL answer to the whole mess in the X11 refresh() // code right now...) board4 *get_board(); unsigned int board_width(); unsigned int board_height(); }; #endif // GAME_H atom4/engine/Conscript0100600000175000017500000000057107644516351014613 0ustar hsteohhsteoh#!/usr/bin/cons # # Atom-4 game engine build script. # # $Id: Conscript,v 1.4 2003/02/17 14:00:07 hsteoh Exp hsteoh $ # Import qw(CONS BINDIR INCDIR LIBDIR); Install $CONS $LIBDIR, 'libatom4.a'; Install $CONS $INCDIR, 'ai.h', 'board4.h', 'color4.h', 'game.h', 'triboard.h'; Library $CONS 'libatom4.a', 'ai.cc', 'board4.cc', 'color4.cc', 'game.cc', 'triboard.cc'; atom4/engine/ai.h0100600000175000017500000000434007645443254013466 0ustar hsteohhsteoh/* * Atom-4 AI player * Header file * * $Id: ai.h,v 1.18 2003/04/11 04:37:47 hsteoh Exp hsteoh $ */ #ifndef AI_H #define AI_H //#define DEBUG_AI #ifdef PROFILE #define PROFILE_AI #endif #ifdef DEBUG_AI #include #endif //DEBUG_AI // TESTING ONLY! #include #include #include "board4.h" #include "event.h" #include "game.h" class atom4ai : public atom4local { int playernum; int search_depth; // game tree search depth #ifdef DEBUG_AI FILE *logfile; void reopen_log(); #endif //DEBUG_AI struct move_t { int x, y; }; struct hdelta { // heuristic function accumulator int changes, pcreated, pdestroyed; inline hdelta operator-() { hdelta neg = { -changes, -pdestroyed, -pcreated }; return neg; } }; //TESTING ONLY!!!!!!!! eventloop *loop; pid_t child; int childpipe; // pipe to child process class childmon : public eventhandler { atom4ai *ai; public: childmon(atom4ai *context) : ai(context) {} void read_ready(eventloop *src, int fd); void write_ready(eventloop *src, int fd); } babysitter; // FIXME: THIS SHOULD BE MOVED TO PARENT CLASS static color4 nextcolor(color4 c) { return c.rotr().complement(); } void find_legal_moves(board4 &board, elist &moves); float score_opponent_turn(board4 &board, color4 nexttile, float min, float max, hdelta h, int maxdepth); void update_heuristic(board4 &board, color4 propagator, elist &undolist, hdelta &h); float calc_heuristic(hdelta h); float score_move(board4 &board, color4 tile, move_t m, float min, float max, hdelta h, int maxdepth); move_t pick_random(elist &list); void pick_best_move(int parentfd); void make_move(); protected: void notify_move(int player, elist &chg); void notify_clear(); public: atom4ai(eventloop *loop, unsigned int width, unsigned int height, int which_player); ~atom4ai(); // AI configuration void set_search_depth(int depth) { search_depth=depth; } // Base class overrides void reset(); // restart game mode_t game_mode(); int local_playernum(); int is_local_turn(); int newround(); }; #endif // AI_H atom4/engine/ai.cc0100600000175000017500000002122107645337706013625 0ustar hsteohhsteoh/* * Atom-4 AI player * Implementation file * * $Id: ai.cc,v 1.23 2003/04/10 15:39:54 hsteoh Exp hsteoh $ */ #include #include #include #include "ai.h" #include "color4.h" #include "exception.h" /* * AI behaviour settings */ #define DEFAULT_SEARCH_DEPTH 2 // Heuristics for nodes at maxdepth=0 #define COLOR_CHANGE_WEIGHT 1.0 #define PCREATED_WEIGHT 1.7 #define PDESTROYED_WEIGHT 1.5 #define WINNING_WEIGHT 1000.0 #ifdef DEBUG_AI void atom4ai::reopen_log() { fclose(logfile); // reopen log file per round logfile=fopen("/tmp/atom4ai.log","w"); if (!logfile) throw exception("atom4ai: failed to open logfile\n"); } #endif //DEBUG_AI #ifdef PROFILE_AI #include extern "C" { extern void _start(void), etext(void); } #endif //PROFILE_AI void atom4ai::childmon::read_ready(eventloop *loop, int fd) { int x,y; int count; int status; // child status if (read(fd, &x, sizeof(x))==-1) throw exception("Error while reading from AI subprocess"); if (read(fd, &y, sizeof(y))==-1) throw exception("Error while reading from AI subprocess"); // Remove handler from event loop loop->unregister_handler(eventloop::READER, fd); close(fd); waitpid(ai->child, &status, 0); // wait for child to terminate ai->child = -1; if (!ai->move(ai->playernum, x,y)) { throw exception("@Unable to move to (%d,%d) as requested by AI subprocess", x,y); } } void atom4ai::childmon::write_ready(eventloop *loop, int fd) { throw exception("atom4ai::childmon registered as a write handler??\n"); } void atom4ai::find_legal_moves(board4 &board, elist &moves) { move_t m; // Scan for all possible legal moves moves.clear(); for (m.y=0; m.y moves; elistiter it; // Find opponent's best response to our move find_legal_moves(board, moves); for (it=moves.headp(); it; it++) { #ifdef DEBUG_AI fprintf(logfile, " (%d) Considering: (%d,%d), min=%4.3f max=%4.3f <<\n", maxdepth, (*it).x, (*it).y, min, max); #endif //DEBUG_AI float score = score_move(board, nexttile, *it, min, max, h, maxdepth); if (score > min) min = score; #ifdef DEBUG_AI fprintf(logfile, " >>(%d) move(%d,%d): %4.3f\n", maxdepth, (*it).x, (*it).y, score); #endif //DEBUG_AI // Alpha prune if (score > max) { #ifdef DEBUG_AI fprintf(logfile, " [(%d) PRUNED: %4.3f > (%4.3f)]\n", maxdepth, score, max); #endif //DEBUG_AI return 1e9; // anything > max will do, actually. } } return min; } void atom4ai::update_heuristic(board4 &board, color4 our_prop, elist &undo, hdelta &h) { elistiter it; color4 enemy_prop = our_prop.complement(); for (it=undo.headp(); it; it++, h.changes++) { color4 newcell = board.getcell((*it).x, (*it).y); color4 oldcell = (*it).cell; if (newcell==our_prop) h.pcreated++; if (oldcell==enemy_prop) h.pdestroyed++; } } float atom4ai::calc_heuristic(hdelta h) { return COLOR_CHANGE_WEIGHT*h.changes + PCREATED_WEIGHT*h.pcreated + PDESTROYED_WEIGHT*h.pdestroyed; } float atom4ai::score_move(board4 &board, color4 tile, move_t m, float min, float max, hdelta h, int maxdepth) { color4 our_prop = tile.propagator(); elist splash, unsplash; float score; int win; // Compute changes caused by move board.compute_splash(m.x, m.y, tile, splash); board.applychanges(splash, &unsplash); update_heuristic(board, our_prop, unsplash, h); if (board.check_win(splash, our_prop, WIN_ROW)) { // Adjust based on real, hard data :-) score = WINNING_WEIGHT + calc_heuristic(h); } else if (maxdepth >0) { // Swap and negate min and max: if opponent is able to make a move that // scores higher than current (-min), we won't make that choice. score = -score_opponent_turn(board, nextcolor(tile), -1e9, -min, -h, maxdepth-1); } else { score = calc_heuristic(h); // exhausted search depth } board.applychanges(unsplash, NULL); // restore original board return score; } atom4ai::move_t atom4ai::pick_random(elist &list) { int p; // 1/probability elistiter it; move_t pick; for (p=1, it=list.headp(); it; p++, it++) { if (rand()%p == 0) { pick = *it; } } return pick; } void atom4ai::pick_best_move(int fd) { elist moves; elist bestmoves; // in case >1 moves have equal score elistiter it; float min=-1e9, max=+1e9; hdelta h = { 0, 0, 0 }; // heuristic function accumulator find_legal_moves(*get_board(), moves); // compute score for each move for (it=moves.headp(); it; it++) { move_t m = *it; #ifdef DEBUG_AI fprintf(logfile, "AI: considering move (%d,%d), max depth=%d {{\n", m.x, m.y, search_depth); #endif //DEBUG_AI float score = score_move(*get_board(), current_tile(), m, min, max, h, search_depth); #ifdef DEBUG_AI fprintf(logfile, "}} move (%d,%d): %.3f\n", m.x, m.y, score); #endif //DEBUG_AI // Update list of good moves if (score > min) { min = score; bestmoves.clear(); // prev moves are worse than this one bestmoves.append(m); // this move is now the best } else if (score==min) { bestmoves.append(m); // more than one best move so far } } // randomly pick a move from the list of highest-scored moves if (bestmoves.num_elem() < 1) throw exception("atom4ai: unable to find valid move?"); move_t m = pick_random(bestmoves); #ifdef DEBUG_AI fprintf(logfile, "AI: moving (%d,%d) --> %.3f\n", m.x, m.y, min); #endif //DEBUG_AI // Send move back to parent process write(fd, &m.x, sizeof(m.x)); write(fd, &m.y, sizeof(m.y)); #ifdef PROFILE_AI _mcleanup(); #endif //PROFILE_AI } // TESTING: spawn subprocess to do the computation, so that the event loop // can continue processing events. void atom4ai::make_move() { int pipefd[2]; if (current_player()!=playernum || child != -1) return; // not our turn if (pipe(pipefd)==-1) throw exception("Unable to create communications channel with AI " "subprocess"); switch(child=fork()) { case -1: throw exception("Unable to create AI subprocess\n"); case 0: // in child close(pipefd[0]); // close read fd #ifdef PROFILE_AI monstartup((u_long)&_start, (u_long)&etext); #endif //PROFILE_AI pick_best_move(pipefd[1]); exit(0); // never return default: // in parent close(pipefd[1]); // close write fd childpipe = pipefd[0]; // remember read fd loop->register_handler(eventloop::READER, childpipe, &babysitter); break; } } void atom4ai::notify_move(int player, elist &chg) { atom4::notify_move(player, chg); // forward notification first make_move(); // if it's now our turn, make a move } void atom4ai::notify_clear() { atom4::notify_clear(); // forward notification make_move(); // move if it's our turn } atom4ai::atom4ai(eventloop *eloop, unsigned int width, unsigned int height, int which_player) : atom4local(width,height), playernum(which_player), search_depth(DEFAULT_SEARCH_DEPTH), loop(eloop), babysitter(this) { #ifdef DEBUG_AI logfile=fopen("/tmp/atom4ai.log","w"); if (!logfile) throw exception("atom4ai: failed to open logfile\n"); #endif //DEBUG_AI child = -1; // indicate not waiting for process // Make first move if we're starting make_move(); } atom4ai::~atom4ai() { if (child != -1) { loop->unregister_handler(eventloop::READER, childpipe); // TODO: kill child process } #ifdef DEBUG_AI fclose(logfile); #endif //DEBUG_AI } void atom4ai::reset() { // NOTE: may want to insert resetting code here if we keep state between // turns atom4local::reset(); #ifdef DEBUG_AI reopen_log(); #endif //DEBUG_AI make_move(); // move if it's our turn after reset } atom4::mode_t atom4ai::game_mode() { return atom4::PEER; } int atom4ai::local_playernum() { // FIXME: can be more general return playernum==1 ? 2 : 1; } int atom4ai::is_local_turn() { return current_player()!=playernum && !round_over(); } int atom4ai::newround() { atom4local::newround(); #ifdef DEBUG_AI reopen_log(); #endif //DEBUG_AI make_move(); // move if it's our turn after reset } atom4/engine/color4.h0100600000175000017500000000114607624235542014274 0ustar hsteohhsteoh/* * Atom-4 triboard celltype wrapper * Header file * * $Id$ */ #ifndef COLOR4_H #define COLOR4_H #include "triboard.h" class color4 { celltype val; public: color4(celltype c=BAD_CELL) : val(c) {} operator celltype () { return val; } int colortype(); // -1 = invalid, 0 = propagator, 1 = additive, // 2 = subtractive color4 propagator() { return propagator(colortype()); } static color4 propagator(int type); color4 complement() { return color4(((val-'a')^7) + 'a'); } color4 mix(color4 c); color4 rotl(); // rotate left color4 rotr(); // rotate right }; #endif // COLOR4_H atom4/engine/board4.h0100600000175000017500000000321507644516351014246 0ustar hsteohhsteoh/* * Atom-4 triboard * Header file * * $Id: board4.h,v 1.4 2003/04/08 09:56:28 hsteoh Exp hsteoh $ */ #ifndef BOARD4_H #define BOARD4_H #include // for NULL #include // MUST be prog/lib version! #include "color4.h" #include "triboard.h" // Representation for a unit of change on the board struct boardchange { int x,y; color4 cell; boardchange(int xcoor, int ycoor, color4 c) : x(xcoor), y(ycoor), cell(c) {} }; class board4 : public triboard { public: board4(unsigned int width, unsigned int height) : triboard(width,height) {} board4(board4 &b) : triboard(b) {} board4 &operator= (board4 &b); ~board4(); // Check if it's legal to place a tile here int check_legal(int x, int y); // Compute color changes caused by actor at (x,y). Note that the actor // itself is included in the delta as the first board change. void compute_splash(int x, int y, color4 actor, elist &delta); // Apply changes specified by &delta. Also computes reverse delta that can // be applied to reverse changes, unless *undo == NULL. void applychanges(elist &delta, elist *undo); // Counts the number of consecutive matching marbles in the given direction // from (x,y). int count_marbles(color4 marble, int x, int y, int dir, int max); // Check if there's a row of N same colored marbles in any direction from // (x,y). int check_row(color4 marble, int x, int y, int length); // Check the locations indicated in &lastchanges to see if there's a winning // row in any of them. int check_win(elist &lastchanges, color4 piece, int rowlen); }; #endif // BOARD4_H atom4/engine/color4.cc0100600000175000017500000000175007624235205014427 0ustar hsteohhsteoh/* * Atom-4 triboard celltype wrapper * Implementation file * * $Id$ */ #include "color4.h" int color4::colortype() { if (val=='a' || val=='h') return 0; if (val=='b' || val=='c' || val=='e') return 1; // additive if (val=='d' || val=='f' || val=='g') return 2; // subtractive return -1; // invalid color } color4 color4::propagator(int type) { switch(type) { case 1: return color4('h'); case 2: return color4('a'); default: return BAD_CELL; } } color4 color4::mix(color4 c) { if (c.colortype() < 0) return c; // c cannot be affected switch(colortype()) { case 1: // additive return color4(((c.val-'a') | (val-'a')) + 'a'); case 2: return color4(((c.val-'a') & (val-'a')) + 'a'); default: return c; } } color4 color4::rotl() { int rawval = val-'a'; return color4(( ((rawval<<1)&7) | ((rawval>>2)&1) ) + 'a' ); } color4 color4::rotr() { int rawval = val-'a'; return color4(( (rawval>>1) | ((rawval&1)<<2) ) + 'a'); } atom4/engine/board4.cc0100600000175000017500000000633107644516350014405 0ustar hsteohhsteoh/* * Atom-4 triboard * Implementation file * * $Id: board4.cc,v 1.3 2003/02/18 22:23:14 hsteoh Exp hsteoh $ */ #include "board4.h" board4 &board4::operator= (board4 &b) { this->triboard::operator= (b); return *this; } board4::~board4() {} int board4::check_legal(int x, int y) { int prev_occupied=0; // 1 if cell has any players' marble; // 0 otherwise int cur_occupied=0; color4 cur_ngbr; int i; // Check that (x,y) is currently unoccupied if (getcell(x,y) != EMPTY_CELL) return 0; // cannot put a marble here // Check neighbours of (x,y). Here, we loop 7 times and take i%6 so that // we will automagically compare dir=0 with dir=5 with no additional code // :-) for (i=0; i<7; i++) { cur_ngbr = ngbr_of(x,y,i%6); cur_occupied = (cur_ngbr.colortype() != -1); if (cur_occupied && prev_occupied) { return 1; // found adjacent pair } prev_occupied = cur_occupied; } return 0; // no neighbouring pairs to attach to } void board4::compute_splash(int x, int y, color4 actor, elist &delta) { color4 propagator = actor.propagator(); int i; delta.clear(); if (propagator==BAD_CELL) return; // no propagator found; nothing to do delta.append(boardchange(x,y,actor)); for (i=0; i<6; i++) { int ox=x, oy=y, nx, ny; color4 affected; // Propagate effect over any propagators in this direction while (ngbr_coor(ox,oy,i,nx,ny) && getcell(nx,ny)==propagator) { ox=nx, oy=ny; } affected = getcell(nx,ny); if (affected.colortype() != -1) { color4 result = actor.mix(affected); if (result != affected) // only add delta if actually changed delta.append(boardchange(nx, ny, result)); } } // endfor } void board4::applychanges(elist &delta, elist *undo) { elistiter it; if (undo) undo->clear(); for (it=delta.headp(); it; it++) { int x=(*it).x, y=(*it).y; if (undo) undo->append(boardchange(x,y, getcell(x,y))); setcell(x,y, (*it).cell); } } int board4::count_marbles(color4 marble, int x, int y, int dir, int max) { int count=0; while (ngbr_coor(x,y,dir,x,y)) { if (getcell(x,y)==marble) { count++; if (count>=max) return max; } else break; // stop at first non-matching marble } return count; } int board4::check_row(color4 piece, int x, int y, int length) { int nx,ny; int i; if (getcell(x,y)!=piece) return 0; // Check if there is a sufficiently long row of the same color in any of // the 3 pairs of directions for (i=0; i<3; i++) { int ri=(i+3)%6; // reverse direction of i // Scan forwards and backwards for consecutive matching marbles. // The +1 counts the current position (x,y), which is not counted by // count_marbles(). if (count_marbles(piece,x,y,i,length-1) + count_marbles(piece,x,y,ri,length-1) + 1 >= length) { return 1; // found row of at least (length) } } return 0; // no row found } int board4::check_win(elist &lastchanges, color4 piece, int len) { elistiter it; for (it=lastchanges.headp(); it; it++) { if (check_row(piece, (*it).x, (*it).y, len)) return 1; } return 0; } atom4/engine/gametree.h0100600000175000017500000000301307645440521014654 0ustar hsteohhsteoh/* * AI game (sub)tree class * Header file * * $Id$ */ #ifndef GAMETREE_H #define GAMETREE_H #include // MUST be prog/lib/ version! #include "atom4.h" struct move_t { int x, y; inline int operator== (move_t m) { return x==m.x && y==m.y; } }; class gamenode; // forward decl class gameedge { move_t m; // move associated with this edge gamenode *dest; // [O] NULL if not cached public: gameedge(move_t move) : m(move), dest(NULL) {} ~gameedge() { if (dest) delete dest; } inline int operator== (move_t move) { return m==move; } inline gamenode *operator*() { return dest; } // [R]() inline move_t label() { return m; } }; // Game node value heuristic function accumulator // - changes = number of color changes caused by move // - pcreated = number of propagators (ours) created // - pdestroyed = number of propagators (enemy's) destroyed // - cluster = cluster factor for propagators we created // - uncluster = cluster factor for propagators we destroyed struct hdelta { int changes, pcreated, pdestroyed; int cluster, uncluster; // cluster factors inline hdelta operator-() { hdelta neg = { -changes, -pdestroyed, -pcreated, -uncluster, -cluster }; } }; class gamenode { elist children; elist splash, unsplash; int win:1; // 1 if winning state, else 0 hdelta heu; // heuristic function accumulator public: gamenode(); ~gamenode(); }; class gametree { gamenode *root; // [O] public: gametree(); ~gametree(); }; #endif //GAMETREE_H atom4/general/README0100600000175000017500000000011407623323226013737 0ustar hsteohhsteohGeneral stuff that could (and probably should) be integrated into prog/lib. atom4/general/resource.cc0100600000175000017500000000271307644017417015231 0ustar hsteohhsteoh/* * Simple resource management base classes * Implementation file * * $Id$ */ #include // for NULL #include "resource.h" /* FUNCTIONS */ resource::resource() : ref(0) { } resource::~resource() {} void resource::alloc() { if (ref==0) load(); ref++; } void resource::dealloc() { if (ref>0) { ref--; if (ref==0) unload(); } else { throw resource_exception("Resource already unloaded\n"); } } resourceman::resourceman(int static_ids) : num_static_ids(static_ids) { int i; staticres = new resource*[static_ids]; if (!staticres) throw resource_exception("Out of memory"); for (i=0; i=num_static_ids) throw resource_exception("@Bad resource ID: %d\n", id); if (!staticres[id]) { staticres[id] = r; } else { throw resource_exception("@Resource ID %d already initialized\n", id); } } resource *resourceman::allocate(int id) { if (id<0 || id>=num_static_ids) throw resource_exception("@Bad resource ID: %d\n", id); staticres[id]->alloc(); return staticres[id]; } void resourceman::deallocate(int id) { if (id<0 || id>=num_static_ids) throw resource_exception("@Bad resource ID: %d\n", id); staticres[id]->dealloc(); } atom4/general/resource.h0100600000175000017500000000223407644017641015070 0ustar hsteohhsteoh/* * Simple resource management base classes * Header file * * $Id$ */ #ifndef RESOURCE_H #define RESOURCE_H #include /* EXCEPTIONS */ class resource_exception : public exception { public: resource_exception(char *msg) : exception(msg) {} resource_exception(char *fmt, int i) : exception(fmt, i) {} }; /* CLASSES */ // TODO: // - resource dependencies class resource { int ref; // reference counter protected: virtual void load()=0; virtual void unload()=0; public: resource(); virtual ~resource(); // Dynamic loading stuff int is_loaded() { return ref>0; } void alloc(); void dealloc(); }; template class eresource : public resource { res_t res; protected: virtual void load() {} virtual void unload() {} public: eresource(res_t r) : res(r) {} ~eresource() {} operator res_t () { return res; } }; class resourceman { int num_static_ids; resource **staticres; // static resources [O[O]] public: resourceman(int num_static_ids); ~resourceman(); void init_resource(int id, resource *r); // [O] resource *allocate(int id); // [R]() void deallocate(int id); }; #endif // RESOURCE_H atom4/general/Conscript0100600000175000017500000000041707644104056014755 0ustar hsteohhsteoh#!/usr/bin/cons # # Atom-4 general routines build script. # # $Id: Conscript,v 1.1 2003/04/06 20:03:51 hsteoh Exp hsteoh $ # Import qw( CONS INCDIR OBJDIR ); # Products Install $CONS $INCDIR, 'event.h'; Install $CONS $OBJDIR, 'event.o'; Objects $CONS 'event.cc'; atom4/general/event.cc0100600000175000017500000003232707646610574014534 0ustar hsteohhsteoh/* * Simple generic event loop to multiplex the different subsystems we need * Implementation file * * $Id: event.cc,v 1.10 2003/04/14 19:52:18 hsteoh Exp hsteoh $ */ #include #include #include #include "event.h" #include "exception.h" /* * * Class eventhandler * */ eventhandler::~eventhandler() {} /* * * General convenience operators * */ int operator< (timeval t1, timeval t2) { return (t1.tv_sec != t2.tv_sec) ? t1.tv_sec < t2.tv_sec : t1.tv_usec < t2.tv_usec; } int operator<= (timeval t1, timeval t2) { return (t1.tv_sec != t2.tv_sec) ? t1.tv_sec < t2.tv_sec : t1.tv_usec <= t2.tv_usec; } timeval &operator+= (timeval &t1, timeval t2) { t1.tv_usec += t2.tv_usec; if (t1.tv_usec > 1000000) { t1.tv_sec += t1.tv_usec/1000000; t1.tv_usec %= 1000000; } t1.tv_sec += t2.tv_sec; return t1; } // WARNING: this function assumes that 0 <= tv_usec < 1000000 for both // operands. It may return strange results if this is not true. timeval &operator-= (timeval &t1, timeval t2) { t1.tv_usec -= t2.tv_usec; if (t1.tv_usec < 0) { t1.tv_sec--; // borrow a second t1.tv_usec += 1000000; } t1.tv_sec -= t2.tv_sec; return t1; } /* * * Class timerhandler * */ timerhandler::~timerhandler() {} /* * * Class timerqueue * */ int timerqueue::nextid = 0; void timerqueue::insert(timer_entry ent) { elistiter it, prev; // Insert in sorted order prev.invalidate(); for (it=queue.headp(); it; it++) { if (ent < *it) { queue.insert(ent, prev); return; // done } prev=it; } // We're at end of list, just append the new entry. queue.insert(ent, prev); } timerqueue::~timerqueue() { elistiter null; timer_entry ent; // This is necessary because although timer_entry is embedded, timerhandler* // could potentially be an owner pointer. We can't put this in timer_entry's // dtor 'cos we're doing lots of copying around, and reference counts would // likely be incorrect by now. while (queue.num_elem() > 0) { ent = queue.remove(null); if (ent.handler->type() == timerhandler::DYNAMIC) { delete ent.handler; } } } // FIXME: we should be careful of ID uniqueness issues. Shouldn't be a problem // normally, since sizeof(int) is big; but for long running servers this is // a real issue that must be addressed. int timerqueue::schedule(timeval target, timerhandler *handler) { timer_entry ent; // Create new timer entry ent.id = nextid++; ent.type = timer_entry::ONETICK; ent.target = target; ent.handler = handler; insert(ent); // add to queue return ent.id; } int timerqueue::schedule(timeval target, timeval period, timerhandler *handler) { timer_entry ent; // Create new periodic timer ent.id = nextid++; ent.type = timer_entry::PERIODIC; ent.target = target; ent.period = period; ent.handler = handler; insert(ent); return ent.id; } // Does nothing if ID doesn't exist. void timerqueue::unschedule(int id) { elistiter it, prev; prev.invalidate(); for (it=queue.headp(); it; it++) { if ((*it).id==id) { timer_entry ent = queue.remove(prev); if (ent.handler->type()==timerhandler::DYNAMIC) delete ent.handler; } prev=it; } } timeval timerqueue::next_scheduled() { elistiter it = queue.headp(); if (it) { return (*it).target; } else { // FIXME: should throw exception here; it's a bad idea to return 0 which // may cause select() to poll instead of block. timeval t = { 0, 0 }; return t; } } // WARNING: this function can potentially enter a VERY long loop if some of // the timers in the queue have a very ancient target time and a small period. void timerqueue::ticknext(eventloop *src, timeval curtime) { while (queue.headp() && (*queue.headp()).target <= curtime) { elistiter null; timer_entry ent = queue.remove(null); // trigger timer callback ent.handler->tick(src, curtime); // Reschedule timer if periodic if (ent.type==timer_entry::PERIODIC) { // Note: don't use curtime, because we might be a bit late here. ent.target += ent.period; insert(ent); // reschedule timer } else { if (ent.handler->type()==timerhandler::DYNAMIC) delete ent.handler; } } } /* * * Class eventloop * */ elistiter eventloop::search( elist &list, int fd) { elistiter it; for (it=list.headp(); it; it++) { if ((*it).fd == fd) return it; } it.invalidate(); // indicate not found return it; } int eventloop::remove(elist &list, int fd) { elistiter it, prev; for (it=list.headp(); it; it++) { if ((*it).fd == fd) { list.remove(prev); return 1; } prev=it; } return 0; } void eventloop::update_maxes() { elistiter it; max_fd=0; for (it=readers.headp(); it; it++) { if ((*it).fd > max_fd) max_fd = (*it).fd; } for (it=writers.headp(); it; it++) { if ((*it).fd > max_fd) max_fd = (*it).fd; } } void eventloop::make_fdset(elist &list, fd_set *set) { elistiter it; FD_ZERO(set); for (it=list.headp(); it; it++) { int fd = (*it).fd; if (FD_ISSET(fd, set)) // sanity check throw exception("@Internal error: multiple handlers registered for " "fd %d", fd); FD_SET(fd, set); } } timeval *eventloop::calc_wait(timeval *t) { timeval curtime; // If no timers, return NULL so that select() will block indefinitely. if (timers.num_timers()==0) return NULL; *t = timers.next_scheduled(); if (gettimeofday(&curtime, NULL)==-1) throw exception("Unable to get current time: %s", strerror(errno)); if (*t <= curtime) { t->tv_sec = t->tv_usec = 0; // at least one timer pending; force // select() to return immediately } else { *t -= curtime; // interval from now till next timer } return t; } // Notes: // - There is no equivalent for postponed_add() because functionally, deletes // cancels adds, but adds do not necessarily cancel deletes (since the newly // added handler could be different from the one before). void eventloop::postponed_del(elist &addlist, elist &dellist, int fd) { elistiter it, prev, delpred; int need_delete=0; // need flag 'cos delpred can be // legitimately NULL if entry to be // deleted is at the head of the list // Scan addlist for any postponedly added entry that would be cancelled by // this delete operation. Notes: // - this loop finds the *last* occurring match in addlist, since that is // functionally what would be removed had the add not been postponed. prev.invalidate(); delpred.invalidate(); for (it=addlist.headp(); it; it++) { if ((*it).fd == fd) { delpred=prev; // mark for removal need_delete=1; } prev=it; } // Actually remove the entry if any were found. If none were found, this // delete operation affects the real handler list, so we add it to the // postponed deletion list. if (need_delete) { addlist.remove(delpred); } else { dellist.append(fd); } } void eventloop::dispatch(elist &list, fd_set *set, void (eventhandler::*method)(eventloop *src, int fd)) { elistiter it; reentrant_level++; for (it=list.headp(); it; it++) { int fd = (*it).fd; if (FD_ISSET(fd, set)) { ((*it).handler->*method)(this, fd); FD_CLR(fd, set); } } // Sanity check for (int i=0; i &target, elist &delayed) { elistiter it; for (it=delayed.headp(); it; it++) { // (ignore errors; it's too late to report a non-existent fd by now) remove(target, *it); } delayed.clear(); // no more backlog } void eventloop::fill(elist &target, elist &delayed) { elistiter it; for (it=delayed.headp(); it; it++) { handler_entry &newent = *it; if (!search(target, newent.fd)) { target.append(newent); if (newent.fd > max_fd) max_fd = newent.fd; } else { // This probably indicates a horrible reentrance condition that we // haven't covered yet... in any case, things will definitely break if // this happens, even if it's the caller's fault; so we can't just // ignore it. throw exception("@[Delayed] reader already registered for fd %d\n", newent.fd); } } // endforeach(delayed) delayed.clear(); // no more backlog } void eventloop::fire_timers() { timeval curtime; if (gettimeofday(&curtime, NULL)==-1) throw exception("Unable to get current time: %s", strerror(errno)); // Fire off timers timers.ticknext(this, curtime); } eventloop::eventloop() { max_fd = 0; reentrant_level = 0; } eventloop::~eventloop() { } void eventloop::register_handler(type_t type, int fd, eventhandler *handler) { handler_entry newent; newent.fd = fd; newent.handler = handler; if (reentrant_level==0) { if (type==READER || type==READWRITER) { if (!search(readers, fd)) { readers.append(newent); if (fd > max_fd) max_fd = fd; } else { throw exception("@Reader already registered for fd %d\n", fd); } } if (type==WRITER || type==READWRITER) { if (!search(writers, fd)) { writers.append(newent); if (fd > max_fd) max_fd = fd; } else { throw exception("@Writer already registered for fd %d\n", fd); } } } else { // in reentrance; delay addition if (type==READER || type==READWRITER) new_readers.append(newent); if (type==WRITER || type==READWRITER) new_writers.append(newent); } } void eventloop::unregister_handler(type_t type, int fd) { // If we're inside a list-altering function, do NOT attempt to do actual // remove; otherwise we may invalidate iterators and cause problems. if (reentrant_level==0) { if (type==READER || type==READWRITER) { if (!remove(readers, fd)) throw exception("@No reader registered for fd %d, cannot " "unregister\n", fd); } if (type==WRITER || type==READWRITER) { if (!remove(writers, fd)) throw exception("@No writer registered for fd %d, cannot " "unregister\n", fd); } update_maxes(); } else { // we've been re-entered; schedule removal instead of actually doing it if (type==READER || type==READWRITER) postponed_del(new_readers, dead_readers, fd); if (type==WRITER || type==READWRITER) postponed_del(new_writers, dead_writers, fd); } } void eventloop::run(int *exitflag) { int n; fd_set readfds, writefds; timeval maxwait; // max wait time until next timer if (reentrant_level > 0) throw exception("Attempt to call eventloop::run() reentrantly"); while (!*exitflag) { make_fdset(readers, &readfds); make_fdset(writers, &writefds); // Select on fd's // Note: calc_wait() is called immediately here so that it can return // NULL if no timers are waiting. n = select(max_fd+1, &readfds, &writefds, NULL, calc_wait(&maxwait)); if (n==-1) { if (errno!=EINTR) { // ignore EINTR, it's just SIGCONT throw exception("@eventloop select() error: %s\n", strerror(errno)); } } else if (n>0) { dispatch(readers, &readfds, &eventhandler::read_ready); dispatch(writers, &writefds, &eventhandler::write_ready); sync(); // catchup on operations delayed to // prevent reentrancy problems } // Fire off expired timers // (Note: we do this after dispatching, so that any timer handlers that // unregisters event handlers won't invalidate readfds and writefds. Nasty // sequencing problems may arise otherwise. fire_timers(); } } int eventloop::schedule(long sec, long usec, timerhandler *handler) { timeval curtime; timeval target = { sec + usec/1000000, usec % 1000000 }; if (gettimeofday(&curtime, NULL)==-1) throw exception("Unable to get current time\n"); target += curtime; // schedule relative to current time return timers.schedule(target, handler); } int eventloop::schedule(long first_sec, long first_usec, long period_sec, long period_usec, timerhandler *handler) { timeval curtime; timeval target = { first_sec + first_usec/1000000, first_usec % 1000000 }; timeval period = { period_sec + period_sec/1000000, period_usec % 1000000 }; if (gettimeofday(&curtime, NULL)==-1) throw exception("Unable to get current time\n"); target += curtime; // schedule relative to current time return timers.schedule(target, period, handler); } atom4/general/event.h0100600000175000017500000001054607646604637014400 0ustar hsteohhsteoh/* * Simple event loop to multiplex the different subsystems we need * Header file * * $Id: event.h,v 1.7 2003/04/14 19:18:04 hsteoh Exp hsteoh $ * --------------------------------------------------------------------------- * NOTES: * - this requires a POSIX-compliant system, because it uses UNIX fd's and * select(), and gettimeofday() which is a BSD interface. */ #ifndef EVENT_H #define EVENT_H #include #include #include #include #include // MUST be prog/lib version!! class eventloop; // forward decl class eventhandler { public: eventhandler() {} virtual ~eventhandler(); virtual void read_ready(eventloop *src, int fd)=0; virtual void write_ready(eventloop *src, int fd)=0; }; class timerhandler { public: // STATIC should be used if the timer handler is managed by another object; // DYNAMIC means the handler will be automatically destructed once it's // removed from the timer queue. enum type_t { STATIC=0, DYNAMIC }; private: type_t handlertype; public: timerhandler(type_t timertype) : handlertype(timertype) {} virtual ~timerhandler(); virtual void tick(eventloop *src, timeval t)=0; type_t type() { return handlertype; } }; // Convenience functions int operator< (timeval t1, timeval t2); int operator<= (timeval t1, timeval t2); timeval &operator+= (timeval &t1, timeval t2); timeval &operator-= (timeval &t1, timeval t2); // (Used internally by class eventloop) struct timer_entry { int id; enum timer_t { PERIODIC, ONETICK } type; timeval target; timeval period; // if periodic timerhandler *handler; // [R] // Notes: // - the manpage doesn't document this, but tv_usec is basically the // microsecond part of the time, and is always between 0 and 999,999 // inclusive. inline int operator< (timer_entry &t) { return target < t.target; } }; // Scheduler for timers (used internally by class eventloop) class timerqueue { elist queue; static int nextid; void insert(timer_entry ent); public: timerqueue() {} ~timerqueue(); // Returns timer id which can be used to unschedule it int schedule(timeval target, timerhandler *handler); int schedule(timeval target, timeval period, timerhandler *handler); void unschedule(int id); int num_timers() { return queue.num_elem(); } timeval next_scheduled(); // time of next scheduled timer tick void ticknext(eventloop *src, timeval curtime); // trigger all timers // whose target times are <= curtime. }; class eventloop { public: enum type_t { READER, WRITER, READWRITER }; private: int max_fd; struct handler_entry { int fd; eventhandler *handler; // [R] }; elist readers; elist writers; // Timers timerqueue timers; // timer scheduler // Reentrancy-handling stuff int reentrant_level; // >0 means only read-only ops allowed elist dead_readers; elist dead_writers; elist new_readers; elist new_writers; elistiter search(elist &list, int fd); int remove(elist &list, int fd); void update_maxes(); void make_fdset(elist &list, fd_set *set); timeval *calc_wait(timeval *t); void postponed_del(elist &addlist, elist &dellist, int fd); void dispatch(elist &list, fd_set *set, void (eventhandler::*method)(eventloop *src, int fd)); void sync(); // do delayed additions & removals void sweep(elist &target, elist &delayed); void fill(elist &target, elist &delayed); void fire_timers(); public: eventloop(); ~eventloop(); // File descriptors void register_handler(type_t type, int fd, eventhandler *handler); // ([R]) void unregister_handler(type_t type, int fd); // Timers. The schedule() functions return a timer ID which may be used for // cancelling the timer later. // The sec+usec parameters are relative to the current time. int schedule(long sec, long usec, timerhandler *handler); int schedule(long first_sec, long first_usec, long period_sec, long period_usec, timerhandler *handler); inline void unschedule(int timer_id) { // NOOP if ID isn't in timer queue timers.unschedule(timer_id); } void run(int *exitflag); }; #endif // EVENT_H atom4/general/testtimer.cc0100600000175000017500000000262207646604427015426 0ustar hsteohhsteoh/* * Test for timer code */ #include #include "event.h" class timerA : public timerhandler { public: timerA() : timerhandler(STATIC) {} void tick(eventloop *src, timeval t) { printf("(A:%p) Tick (%ld,%ld)\n", this, t.tv_sec, t.tv_usec); } }; class timerB : public timerhandler { int *flag; public: timerB(int *exitflag) : timerhandler(STATIC), flag(exitflag) {} void tick(eventloop *src, timeval t) { printf("(B:%p) Tick (%ld,%ld), setting exit flag\n", this, t.tv_sec, t.tv_usec); (*flag)++; } }; class canceller : public timerhandler { eventloop *loop; int id; public: canceller(eventloop *eloop, int timer_id) : timerhandler(DYNAMIC), loop(eloop), id(timer_id) {} ~canceller() { printf("The reaper departs!\n"); } void tick(eventloop *src, timeval t) { printf("Cancelling timer %d\n", id); loop->unschedule(id); } }; int main() { eventloop loop; int quitflag=0; timerA x, y; timerB z(&quitflag); canceller *reaper; int xid, yid, zid; xid=loop.schedule(10,0, &x); // tick once after 10 seconds yid=loop.schedule(1,0, 2,0, &y); // tick after 1 second, and every 2 // seconds thereafter zid=loop.schedule(30,0, &z); // tick after 30 seconds and set // quit flag reaper = new canceller(&loop, yid); loop.schedule(25,0, reaper); // kill timer y after 25 seconds // Begin the fun! loop.run(&quitflag); } atom4/net/net.cc0100600000175000017500000001603007646671427013347 0ustar hsteohhsteoh/* * Atom-4 Network Protocol utility functions * Implementation file * * $Id: net.cc,v 1.12 2003/04/15 02:48:11 hsteoh Exp hsteoh $ */ #include #include #include #include #include #include #include #include #include "net.h" #define IS_TERMINATOR(x) ((x)>=0x00 && (x)<=0x1f) /* * * CLASS netparser * */ void netparser::skip_spaces() { while (raw[curpos] && raw[curpos]==' ') curpos++; } netparser::netparser(char *message) { reset(); if (message) parse(message); } netparser::~netparser() { reset(); } void netparser::parse(char *message) { reset(); // kill any existing buffers // Find real message length (this is to weed out newlines and other crap // that we might get from the network socket) raw = message; for (rlen=0; raw[rlen] && !IS_TERMINATOR(raw[rlen]); rlen++); raw[rlen] = '\0'; // terminate message properly // Parse first word skip_spaces(); type = next_word(); } void netparser::reset() { raw=type=NULL; rlen=curpos=0; } char *netparser::next_word() { char *word = &raw[curpos]; int end; // Find end of word while (raw[curpos] && raw[curpos]!=' ') curpos++; end=curpos; skip_spaces(); // must do this first, 'cos // skip_spaces() won't go past \0 raw[end] = '\0'; // null-terminate word return word; } char *netparser::get_rest() { char *rest=&raw[curpos]; curpos=rlen; // bump curpos to end of buffer return rest; } /* * * CLASS netconn * */ void netconn::scan_for_packets() { int i=0; int packet_start; while (i packet_start) { process_packet(&rcvbuf[packet_start]); // forward to derived class } // (ignore if 0 length) } } else { // truncate mode // Ignore everything until we find a terminator for (i=0; i=0 && start<=NET_LINE_LIMIT); wrapsize = NET_LINE_LIMIT-start; if (wrapsize>0) { memmove(&rcvbuf[0], &rcvbuf[start], wrapsize*sizeof(char)); } // Update end of buffer pointer rcvbuf_end -= start; } netconn::netconn(int sockfd, eventloop *eloop, int sendqueue_limit) : loop(eloop), sock(sockfd), sendlimit(sendqueue_limit) { rcvbuf_end = 0; truncate = 0; // Register with event loop loop->register_handler(eventloop::READER, sock, this); } netconn::~netconn() { loop->unregister_handler(eventloop::READER, sock); if (sendqueue.num_elem() > 0) { flush(); // (flush() should've unregistered the writer after sending last packet) } close(sock); // close client socket fprintf(stderr, "Client socket (%d) closed\n", sock); } int netconn::send_packet(char *fmt, ...) { int rc; va_list args; va_start(args, fmt); rc = vsend_packet(fmt, args); va_end(args); return rc; } int netconn::vsend_packet(char *fmt, va_list args) { int i, count; char *sendbuf; // Don't bother if outgoing queue is full. if (sendqueue.num_elem() >= sendlimit) return 0; sendbuf = new char[NET_BUFFER_SIZE]; if (!sendbuf) return 0; // out of memory count=vsnprintf(sendbuf, NET_LINE_LIMIT, fmt, args); if (count >= NET_LINE_LIMIT) { // packet got truncated at 1024 bytes; append packet terminator. sendbuf[NET_LINE_LIMIT]='\0'; count=NET_LINE_LIMIT; } // Verify packet validity for (i=0; iregister_handler(eventloop::WRITER, sock, this); } sendqueue.append(sendbuf); return 1; } void netconn::read_ready(eventloop *src, int fd) { int avail_len=NET_LINE_LIMIT - rcvbuf_end; // don't use NET_BUFFER_SIZE // so that we have room for '\0'. int len; // number of bytes received assert(fd==sock); if (avail_len<=0) return; // buffer full: nothing to do len=recv(sock, &rcvbuf[rcvbuf_end], avail_len, MSG_NOSIGNAL); if (len > 0) { rcvbuf_end += len; scan_for_packets(); } else { // Note: we're assuming that recv() returns 0 bytes if we hit EOF if (len==0) { disconnected(); } else { // FIXME: handle recv() errors } } } // FIXME: this function sometimes may block, if the system socket buffer is // for whatever reason smaller than our max packet size. We could use send()'s // MSG_DONTWAIT option, and keep track of partial buffers. But that's more // trouble than it's worth for now. void netconn::write_ready(eventloop *src, int fd) { char *packet, *cp; // [O] int count; //fprintf(stderr, "Write ready...\n"); assert(fd==sock); assert(sendqueue.num_elem() > 0); // Retrieve packet for sending packet = cp = sendqueue.remove(sendqueue.headp()); fprintf(stderr, "Sending packet: %s\n", packet); count = strlen(packet); packet[count] = '\n'; // be nice to telnet count++; // +1 to include packet terminator do { int sent; sent = send(sock, cp, count, MSG_NOSIGNAL); if (sent>0) { count -= sent; cp += sent; } else { if (errno==EPIPE) { disconnected(); // handle error state delete packet; return; } else { // FIXME: error occurred, throw exception } } } while (count); delete [] packet; // done with packet buffer if (sendqueue.num_elem()==0) { // If no more pending packets to send, remove ourselves from the event // loop to avoid spinning fprintf(stderr, "No more pending packets: unregistering as writer\n"); loop->unregister_handler(eventloop::WRITER, sock); } } void netconn::flush() { while (sendqueue.num_elem() > 0) { // Simulate write-ready state until all packets are sent write_ready(loop, sock); } } atom4/net/net.h0100600000175000017500000000736407646620304013207 0ustar hsteohhsteoh/* * Atom-4 Network Protocol utility functions * Header file * * $Id: net.h,v 1.8 2003/04/14 20:57:22 hsteoh Exp hsteoh $ */ #ifndef NET_H #define NET_H #include #include // for size_t #include // MUST be prog/lib version! #include "event.h" #define NET_LINE_LIMIT 1024 // as recommended by protocol #define NET_BUFFER_SIZE ((NET_LINE_LIMIT)+1) // A network message parser. This class is mainly for providing convenience // message parsing functions. class netparser { char *raw; // [R] int rlen; // length of *raw char *type; // [R] first word of packet int curpos; // current position in buffer void skip_spaces(); public: netparser(char *message=NULL); // [R] *message MUST be null-terminated ~netparser(); // Begin parsing a new message. Note that *message will be ALTERED as part // of the parsing process. The caller is responsible for saving a copy of // the original message if needed. void parse(char *message); // Abort parsing of current message, and reset parser state void reset(); // Returns first word in packet. NULL if there is no word in packet. // Note that this does *not* consume any characters from the buffer. char *packet_type() { return type; } // [R] // Calling this multiple times will retrieve individual arguments in the // packet. // - the first word in the packet is NEVER returned by this function; // use packet_type() instead. The first word returned is always the // first argument (i.e. second word in packet) // - Returns the empty string if there are no more arguments in the packet. char *next_word(); // [R]() // Returns the remainder of the packet. Note that this will consume all // remaining characters in packet. Returns NULL if there are no more // arguments left in the packet. char *get_rest(); }; // A generic network connection. // // Currently, this class implements line length limits and truncation as // described in the protocol. class netconn : public eventhandler { eventloop *loop; // [R] int sock; // network socket int sendlimit; // max queued outgoing packets before // exception is thrown char rcvbuf[NET_BUFFER_SIZE]; // network buffer (circular buffer) size_t rcvbuf_end; // index of first free byte in buffer odlist sendqueue; // outgoing packets waiting for // write-ready condition on socket int truncate:1; // 1 if in truncate mode, 0 otherwise void scan_for_packets(); // scan buffer for completed packets void wrap_buffer(int start); // shift buffer contents (start) bytes // backwards. protected: // Conditions to be handled by derived class: // - process_packet() is called every time a completed packet is available // - disconnected() is called when we detect that the socket has been // disconnected prematurely. The derived class should immediately // trigger a cleanup process to remove this connection from the pool. virtual void process_packet(char *packet)=0; virtual void disconnected()=0; public: netconn(int sock, eventloop *loop, int sendqueue_limit); virtual ~netconn(); // Queue packet for sending int send_packet(char *fmt, ...); int vsend_packet(char *fmt, va_list args); // Access functions int sockfd() { return sock; } // Pure virtuals overridden from base class virtual void read_ready(eventloop *src, int fd); virtual void write_ready(eventloop *src, int fd); // Force sending of all queued outgoing packets. By default, send_packet() // only queues packets for sending, and waits until a write_ready() before // actually sending data into the socket. This function forces all pending // packets to be written into the socket. // NOTE: this function may block. void flush(); }; #endif // NET_H atom4/net/server.cc0100600000175000017500000003514007647067031014061 0ustar hsteohhsteoh/* * Atom-4 network server * Implementation file * * $Id: server.cc,v 1.14 2003/04/15 20:39:28 hsteoh Exp hsteoh $ * -------------------------------------------------------------------------- * PROTOCOL * * Please see protocol.txt. */ #include #include #include #include #include #include #include #include // must be prog/lib version #include "server.h" // FIXME: temporary #define GAME_VERSION "2.1" #define PROTOCOL_VERSION "2.0" // Max allowed pending output packets per client (note: this must also account // for multiple packets sent between returns to the event loop) #define SEND_LIMIT 24 /* * Internal tables */ // Map board cells to network representation char cell2net[NUM_COLORS] = { 'K', 'r', 'g', 'y', 'b', 'p', 'c', 'W' }; /* * * Class clientconn * */ clientconn::dispatch clientconn::dispatch_tbl[] = { { "NAME", &clientconn::p_name }, { "REQU", &clientconn::p_requ }, { "MOVE", &clientconn::p_move }, { "RSGN", &clientconn::p_rsgn }, { "CHAT", &clientconn::p_chat }, { "NEXT", &clientconn::p_next }, { "PLAY", &clientconn::p_play }, { "WATCH", &clientconn::p_watch }, { "QUIT", &clientconn::p_quit }, { "WHO?", &clientconn::p_who }, { NULL, NULL } }; void clientconn::p_name() { not_implemented(); } void clientconn::p_requ() { // Client wants a board update send_board(serv->get_board()); } void clientconn::p_move() { int x,y; try { x = next_number(); y = next_number(); } catch(exception &e) { // catch syntax errors send_packet("GRR 802 %s", e.message()); return; } if (playnum) { switch (serv->move(playnum,x,y)) { // attempt move case 1: // good move break; case 0: // bad move send_packet("GRR 103 Illegal move"); break; case -1: send_packet("GRR 102 Game hasn't started yet"); break; case -2: send_packet("GRR 102 Not your turn to move"); break; default: // FIXME: internal error break; } } else { send_packet("GRR 101 You are not a player in the current game"); } } void clientconn::p_rsgn() { not_implemented(); } void clientconn::p_chat() { not_implemented(); } void clientconn::p_next() { not_implemented(); } void clientconn::p_play() { // Sanity checks if (playnum != 0) { send_packet("GRR 104 Already playing"); return; } // FIXME: should check for atom4::round_over() as well // Assign player number playnum = serv->assign_player(id); if (playnum) { send_packet("PLNUM %d", playnum); } else { send_packet("PLNUM W"); } } void clientconn::p_watch() { if (playnum==0) { send_packet("PLNUM W"); // indicate client is a watcher } else { send_packet("GRR 104 Already playing, use RSGN to resign"); } } void clientconn::p_quit() { disconnected(); // client wants to quit } void clientconn::p_who() { serv->send_names(id); // request for name list } void clientconn::fatal_error(char *fmt, ...) { va_list args; // Transmit error message va_start(args, fmt); send_packet(fmt, args); va_end(args); // Schedule disconnection disconnected(); } void clientconn::not_implemented() { send_packet("GRR 802 Not implemented yet"); } int clientconn::next_number() { char *cp = parser.next_word(); char *ep; int val; if (strlen(cp) > 0) { val = strtol(cp, &ep, 10); if (cp==ep || *ep!='\0') throw exception("@Malformed number: %s", cp); return val; } else { throw exception("Missing numerical argument"); } } void clientconn::handshake() { if (strcmp(parser.packet_type(), "ATOM4")!=0) { fatal_error("ERR 902 Bad response to handshake"); return; // abort } if (strcmp(parser.next_word(), "CLNT")!=0) { fatal_error("ERR 902 Handshake expecting CLNT"); return; } if (strcmp(parser.next_word(), GAME_VERSION)!=0) { fatal_error("ERR 901 Incompatible game version, server runs %s", GAME_VERSION); return; } if (strcmp(parser.next_word(), PROTOCOL_VERSION)!=0) { fatal_error("ERR 901 Incompatible protocol version"); return; } // Handshake complete, enter connected state send_packet("ATOM4 CONN Welcome to Atom-4 (%s) server (%s)", GAME_VERSION, PROTOCOL_VERSION); enter_connected_state(); } void clientconn::enter_connected_state() { fprintf(stderr, "Entering connected state\n"); connstate=CONNECTED; playnum=0; // assume watcher mode by default serv->send_names(id); send_board(serv->get_board()); // send initial game board if (serv->need_players()) { send_packet("POW?"); // offer client to play or watch } } void clientconn::connected_state() { dispatch *dp; for (dp=&dispatch_tbl[0]; dp->packet_type; dp++) { if (!strcasecmp(dp->packet_type, parser.packet_type())) { (this->*(dp->handler))(); // invoke handler return; } } // Unable to find handler for request send_packet("GRR 802 Unknown request: %s", parser.packet_type()); } // Notes: // - we should not directly invoke server::disconnect_client() here because it // will return to an invalid (dealloc'd) object context. So we should just // enter DISCONN state, drop any additional incoming packet, and schedule // for disconnect_client() to be called afterwards. void clientconn::process_packet(char *packet) { parser.parse(packet); switch (connstate) { case HANDSHAKE: handshake(); break; case CONNECTED: connected_state(); break; case DISCONN: break; // ignore all incoming packets default: throw exception("@Inconsistent clientconn state %d\n", connstate); } } // Notes: // - server::disconnect_client() doesn't actually disconnect until the event // loop has finished dispatching; so we temporarily enter the DISCONN state // to avoid doing anything more until the actual disconnect happens. void clientconn::disconnected() { if (connstate != DISCONN) { connstate=DISCONN; // ignore all further communications serv->disconnect_client(id); // schedule disconnect } } clientconn::clientconn(server *s, int cid, int sock, char *hostname, eventloop *loop, int sendlimit) : netconn(sock, loop, sendlimit), serv(s), id(cid), hostname(hostname), connstate(HANDSHAKE), playnum(0) { //fprintf(stderr, "Client connection initialized\n"); // Initiate handshake if (!send_packet("ATOM4 SERV %s %s", GAME_VERSION, PROTOCOL_VERSION)) { throw exception("@Error while sending handshake to client on fd %d\n", sockfd()); } //fprintf(stderr, "Sent handshake, waiting for response\n"); } clientconn::~clientconn() { fprintf(stderr, "Client (fd %d) disconnected\n", sockfd()); delete [] hostname; } void clientconn::send_board(board4 *b) { int w,h; int x,y; w = b->width(); h = b->height(); // Send board dimensions send_packet("BDIM %d %d", w, h); char *rowbuf = new char[w*2 + 2]; for (y=0; ygetcell(x,y); if (cell==EMPTY_CELL) { rowbuf[x*2 + y%2] = '.'; } else if (cell>='a' && cell<='h') { rowbuf[x*2 + y%2] = cell2net[(cell-'a')]; } else { delete rowbuf; throw exception("@Internal error: unknown cell value %d\n", (char)cell); } rowbuf[x*2 + y%2 + 1] = ' '; // delimit cells with spaces } // endfor(x) rowbuf[w*2 + 1] = '\0'; // make sure result is null-terminated // Transmit row if (!send_packet("BROW %2d %s", y, rowbuf)) throw exception("@Unable to send board row \"%s\"", rowbuf); } // endfor(y) delete rowbuf; // discard row buffer send_packet("BEND"); // indicate end of board data } void clientconn::round_over() { playnum=0; } /* * * Class server * */ void server::serversock::read_ready(eventloop *src, int fd) { serv->new_client(); // accept connection } void server::serversock::write_ready(eventloop *src, int fd) {} void server::disconnector::tick(eventloop *src, timeval t) { if (serv->clients[cid]) { //fprintf(stderr, "Disconnecting client %d\n", cid); delete serv->clients[cid]; // disconnect client serv->clients[cid] = NULL; // TBD: clean up if client was a player } else { // Not sure about this; we might need to check for repeated disconnect // requests for the same client and ignore subsequent disconnect timers. throw exception("@Attempt to disconnect non-existent client %d", cid); } } /* FIXME: this function, as well as other, general, server-related stuff, * really should be in a base class which we can reuse for future servers! */ /* Creates a socket for listening to connections on the given port. * Returns socket fd if successful, otherwise returns -1. * On return sockname contains the structure for socket name. */ int server::create_servsock(int port, struct sockaddr_in *sockname, int backlog) { int sock; /* Create inet-domain socket, stream-type connection, default (0) protocol */ sock = socket(PF_INET, SOCK_STREAM, 0); if (sock<0) { perror("Socket error"); return -1; } /* Bind the socket address. The htons() and htonl() calls are to convert host * byte order to network byte order. */ sockname->sin_family = AF_INET; sockname->sin_port = htons(port); sockname->sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sock, (struct sockaddr*)sockname, sizeof(*sockname)) < 0) { perror("Socket bind error"); close(sock); return -1; } /* Listen for connections */ if (listen(sock, backlog)) { perror("Cannot listen to socket"); close(sock); /* discard socket: can't listen */ return -1; } return sock; } int server::new_client() { struct sockaddr_in adrs; // client's address socklen_t adrs_len=sizeof(adrs); struct hostent *hostinfo; // for obtaining client's DNS name int csock, i; char *hostname; //fprintf(stderr, "Got client connection\n"); // Accept the connection csock = accept(sock, (struct sockaddr *)&adrs, &adrs_len); if (csock==-1) return 0; // Error occurred //fprintf(stderr, "Client socket is %d\n", csock); // Find out where the client is connecting from hostinfo = gethostbyaddr((char*)&adrs.sin_addr, adrs_len, AF_INET); if (hostinfo) { hostname = new char[strlen(hostinfo->h_name)+1]; strcpy(hostname, hostinfo->h_name); } else { // Unable to determine hostname; use numeric IP instead char *ipadrs = inet_ntoa(adrs.sin_addr); hostname = new char[strlen(ipadrs)+1]; strcpy(hostname, ipadrs); } fprintf(stderr, "New client from %s\n", hostname); // Find available client ID for (i=0; ivsend_packet(fmt, args); va_end(args); if (!rc) throw exception("Error while broadcasting packet: %s ...", fmt); } } } void server::reset_players() { int i; for (i=0; iround_over(); players[i] = NULL; } } } server::server(atom4 *engine, eventloop *eloop, int listenport, int opts) : loop(eloop), listener(this) { int i; game = engine; port = listenport; options = opts; // Init client table for (i=0; iregister_handler(eventloop::READER, sock, &listener); } server::~server() { int i; for (i=0; ischedule(0,0, new disconnector(this, cid)); } void server::send_names(int cid) { int i; if (!clients[cid]) throw exception("@Illegal client ID %s", cid); for (i=0; isend_packet("NAME %d:%s %d client%d %s", i, c->hostname, c->playnum, i, (c->playnum==game->current_player()) ? "[current turn]" : "" ); } } } int server::need_players() { int i; for (i=0; icurrent_player(); if (curpl != -1) { broadcast("TURN %d %c", curpl, cell2net[game->current_tile()-'a']); } } return plnum; } int server::move(int player, int x, int y) { // Sanity checks if (player<1 || player>2) throw exception("@move(): bad player number %d", player); if (!players[player-1]) throw exception("@No such player: %d", player); if (game->current_player()==-1 || need_players()) return -1; if (game->current_player()!=player) return -2; // Make the move if (!game->move(player,x,y)) { return 0; // bad move } else { // good move // FIXME: broadcast board changes if (game->round_over()) { int winner = game->winner(); if (winner==STALEMATE) { broadcast("DRAW"); } else if (winner!=-1) { broadcast("WIN %d", winner); } else { // FIXME: unexpected state, internal error } reset_players(); } else { int curpl = game->current_player(); if (curpl!=-1) { broadcast("TURN %d %c", curpl, cell2net[game->current_tile()-'a']); } } return 1; } } atom4/net/server.h0100600000175000017500000001012707647067033013723 0ustar hsteohhsteoh/* * Atom-4 network server * Header file * * $Id: server.h,v 1.12 2003/04/15 20:39:28 hsteoh Exp hsteoh $ * -------------------------------------------------------------------------- * PROTOCOL * * Please see protocol.txt. */ #ifndef SERVER_H #define SERVER_H #include #include #include #include "event.h" #include "game.h" #include "net.h" #define MAX_CLIENTS 12 // NOTE: must be < FD_SETSIZE #define SERVER_BACKLOG 2 // socket backlog limit class clientconn : public netconn { friend class server; server *serv; // [R] int id; // client ID as registered in *serv char *hostname; // [O] netparser parser; // packet parser enum state_t { // connection state HANDSHAKE=1, // initial handshake CONNECTED=3, // connected DISCONN=10 // to be disconnected } connstate; int playnum; // 1 or 2 if playing, 0 if watching // Protocol dispatch table static struct dispatch { char *packet_type; void (clientconn::*handler)(); } dispatch_tbl[]; // Dispatch functions void p_name(); void p_requ(); void p_move(); void p_rsgn(); void p_chat(); void p_next(); void p_play(); void p_watch(); void p_quit(); void p_who(); // Convenience functions void fatal_error(char *fmt, ...); void not_implemented(); int next_number(); // parse next word in parser as number // Processors for incoming packets void handshake(); void enter_connected_state(); // initial stuff to be done when client // first enters connected state void connected_state(); // Base class pure virtuals void process_packet(char *packet); void disconnected(); public: // ([O]) clientconn(server *s, int cid, int sock, char *hostname, eventloop *loop, int sendlimit); ~clientconn(); void send_board(board4 *b); int plnum() { return playnum; } void round_over(); }; class server { public: enum opt_t { // can be OR'd together LOCAL_ONLY=1, // listen only on loopback address ALLOW_OBSERVERS=2 // allow clients to connect to watch, // but not play. (TBD) }; private: eventloop *loop; // [R] int options; // technically, many OR'd opt_t's inline int opt(opt_t flag) { return options & flag; } // Network-specific stuff int port; // listen port int sock; // listen sock struct sockaddr_in sockname; // socket address class serversock : public eventhandler { server *serv; // server object public: serversock(server *s) : serv(s) {} void read_ready(eventloop *src, int fd); void write_ready(eventloop *src, int fd); } listener; // Client disconnector class disconnector : public timerhandler { server *serv; int cid; public: disconnector(server *s, int client_id) : timerhandler(DYNAMIC), serv(s), cid(client_id) {} void tick(eventloop *src, timeval t); }; fd_set csocks; // client sockets int csocks_max; // max fd's in csocks clientconn *clients[MAX_CLIENTS]; // [O] // Game-specific stuff atom4 *game; // [R] game engine clientconn *players[NUM_PLAYERS]; // [R] int create_servsock(int port, struct sockaddr_in *sockname, int backlog); int new_client(); void broadcast(char *fmt, ...); // broadcast message to all clients void reset_players(); public: server(atom4 *engine, eventloop *loop, int port, int options); ~server(); void disconnect_client(int cid); // Requests for global information void send_names(int cid); // Game flow functions // - need_players() returns non-zero if there are still slots available for // joining the current game // - assign_player() assigns the client to a player number if available, and // returns the player number; otherwise returns 0 (watcher). // - move() makes a move. The following codes are returned: // 1 Move successful // 0 Illegal move // -1 Game not started yet // -2 Not the player's turn to move int need_players(); // are there still playing slots left? int assign_player(int cid); int move(int player, int x, int y); board4 *get_board() { return game->get_board(); } }; #endif // SERVER_H atom4/net/protocol.txt0100600000175000017500000006754307647045413014662 0ustar hsteohhsteoh----------------------- ATOM-4 Network Protocol Version: 2.0 ----------------------- I. Protocol Definitions The ATOM-4 network protocol is a line-based, ASCII text protocol. It is assumed to be transmitted over a reliable, connection-based transport protocol (such as TCP). This protocol governs the communications between the "server", an implementation of the protocol that hosts the ATOM-4 game server, and a "client", representing a prospective player of the game, possibly providing a user interface to a human player. A "line" is defined to be any sequence of bytes which are not line-terminators, followed by one or more delimiting characters. A line-terminator is any character from ASCII 0x00 to ASCII 0x1F. Under this protocol, a "message" is equivalent to a "line". I.A. Line Length Limit This protocol recommends a line length limit of 1024 characters (not counting any line-terminators), although it does not preclude implementations which support arbitrary-length lines. Longer lines may be subject to Truncation; hence implementations supporting long lines must have provision for dealing with servers or clients that enforce the 1024 character limit. Implementations may perform Truncation at a higher line-length limit than 1024 characters. However, implementations MUST NOT perform truncation at less than 1024 characters. I.B. Truncation Truncation is the state the recipient of a transmission enters, when it encounters a line longer than 1024 characters. In the Truncation state, the recipient should treat the first 1024 characters of the line as though it were terminated immediately after the 1024th character. Subsequent characters received from the communications line will be completely ignored until the first line-terminator is found, at which point, the recipient must exit Truncation mode and resume normal protocol communications. I.C. General Packet Structure Under this protocol, packets (lines) are a sequence of "words", delimited by one or more space characters (ASCII 32). Implementations are required to treat multiple consecutive spaces as a single space. The first and last words in a packet may be surrounded with extra spaces as well. Implementations are required to handle this correctly. A "word" is any consecutive sequence of characters which are neither spaces nor terminators. I.D. Game Objects I.D.1. Board Cell Representation The cells transmitted via the BPOS and BROW server connected state messages are single space-delimited characters, each representing a single cell on the game board. Because of protocol limitations on the characters that are permissible in a packet, the internal game board representation is not used; instead, the following mapping is used: Game Object Representation ----------- -------------- Empty cell . Black ball K Red ball r Green ball g Blue ball b Cyan ball c Purple ball p Yellow ball y White ball W II. Protocol Overview The protocol can be divided into two main stages: the Initial Handshake and the Connected State. The Initial Handshake consists of the exchange of a fixed sequence of protocol messages. The Connected State is a state where the Connected State messages may be freely exchanged, in any sequence. Unexpected messages during the Initial Handshake is valid grounds for terminating the communications channel. However, implementations should be prepared to handle Connected State messages in any order once in the Connected State. III. Initial Handshake The Initial Handshake occurs immediately after a client establishes a communications channel to the server. III.A. Server Greeting Once the client has established the connection, the server must sent an initial message, of the form: ATOM4 SERV where is the version number, in the form . where and are positive integers, of the ATOM-4 game engine it is running; and is the version of this protocol that it is using. III.B. Client Response The client, upon receipt of the initial server message, must determine if the given version numbers are compatible with it. If not, it must immediately terminate the connection. Otherwise, it must respond to the server with a message of this form: ATOM4 CLNT where is the version number of the ATOM-4 game it is designed to interact with, and is the version of this protocol that it is using. III.C. Server Confirmation Upon receiving the client's response, the server must determine if the client's version numbers are compatible with it. If it does not support the client's version of the ATOM-4 game or the protocol version, it must terminate the connection (possibly sending an error message---see the section describing protocol error messages). Otherwise, possibly after adjusting its own protocol so as to be maximally compatible with the client, the server must send a confirmation to the client in a message of the form: ATOM4 CONN where is an arbitrary welcome message, which the client may optionally display to its user. Upon transmission of this message, the server should immediately enter the Connected State. Upon receipt of this message, the client should also enter the Connected State. IV. Connected State In the Connected State, the server may freely transmit any Server Message to the client; and the client may freely transmit any Client Message to the server, in any order, until the communications channel is terminated. Collectively, Server Messages and Client Messages are referred to as Connected State messages. Neither server nor client should fail upon receiving a Connected State message in an order it does not expect. However, Client Messages sent at inappropriate times may be ignored by the server. IV.A Client Modes During the Connected State, the client may be either a Player, or a Watcher. A Player is a client who participates in an ongoing game; a Watcher is a client who is connected to the server, and receiving the server's informational updates, but not directly participating in the current game. By default, once the client receives the server's "ATOM4 CONN" confirmation message, it should assume itself to be a Watcher until the server specifies otherwise with a PNUM message. Although the server will ignore any attempt by a Watcher to take direct part in the game, this is not recommended client behaviour. Once the client has received the appropriate PNUM message (see section V), it should switch to Player mode. The player number argument in the PNUM message will indicate to the client which Player it is, in the game. As a Player, the client should expect to receive a TURN message, which the server will send to indicate which player is to move next. The client MUST recognize its player number and respond to it with an appropriate MOVE message, after potentially prompting the user to make a move. The client should NOT respond to a TURN message if the argument to the message does not match its player number (i.e., it should not try to move if it's not its turn to do so). Clients that persistently attempt to respond to TURN messages not addressed to them may be considered abusive by the server; the server may forcefully terminate communications with such clients. While in Player mode, if the server sends a PNUM message with a 'W' parameter (see section V), the client should switch back to Watcher mode, and refrain from sending any gameplay messages. V. Connected State Messages V.A. Server Messages Server Messages can be grouped into the following categories: - Offers: messages sent by the server to ask the client for a response. - Notifications: messages sent by the server to update the client about game events, the state of the game board, etc.. - Non-fatal errors: warnings or other game-related informational messages sent to the client. Non-fatal errors should not cause the server to terminate the client's connection. - Fatal errors: error messages caused by protocol errors, server internal errors, or other problems. The client should expect to be disconnected by the server upon receiving a fatal error message. V.A.1. Offers V.A.1.a. POW? This message is an invitation to the client to join in a new game. The format of this message is: POW? The client should respond with a WATCH or a PLAY. Note that this message does NOT guarantee the client will become a Player, since another client may respond first and occupy all existing positions. The client MUST wait for a PNUM message before entering Player mode. "POW?" stands for "Play Or Watch?". V.A.1.b. TURN This message is a notification from the server to a *particular client*, which may not necessarily be the recipient of the message, that it is its turn to make a move. The format of this message is: TURN

where

is the player number and is the piece the player is currently playing. Clients MUST remember which player number they were assigned by the server, and MUST NOT respond to a TURN message unless their player number is

. If the client which this offer is directed at does not respond, the server may, after a reasonable timeout, withdraw the offer by disconnecting the client and aborting the current game. Upon receiving this message, if

matches the client's assigned player number, it should prompt its user to make a move, and forward the move to the server in the form of a MOVE message. Clients should NOT send multiple MOVE messages to a single TURN message. If the move was invalid, it is the responsibility of the server to indicate this with an appropriate (non-fatal) error message, and to send another TURN message. If

does not match the client's assigned player number, it should inform its user that the game is waiting for player

to make a move. The server should ignore MOVE messages from clients which it did not send the corresponding TURN message to. It may optionally disconnect the client for abusive behaviour if the client persists in doing this. Note the client MAY send other messages to the server (such as requests for updated game board information) between receiving a TURN message and sending a MOVE message. The server should honor such requests, and MUST NOT repeat the TURN message unnecessarily in such cases. V.A.2. Notifications V.A.2.a. PNUM This message is sent to the client to indicate whether it should enter Player mode or Watcher mode. The format of this message is: PNUM

where

may be one of 1, 2, or W. If

is 1 or 2, this is the client's assigned player number, and the client MUST from this point on recognize TURN messages carrying the same number, and should respond to them appropriately. If

is W, then the client MUST switch into Watcher mode, and refrain from sending MOVE messages to the server until notified otherwise by another PNUM message from the server. The server MUST NOT assign the same number to two different clients during the same game. V.A.2.b. BDIM This message is sent by the server to inform the client of the dimensions of the game board. The format of this message is: BDIM where and are positive integers represented as sequences of ASCII digits in decimal. This message invalidates any previous game board updates the server has sent to the client. The client should assume that the game board is now completely empty, and should update all local copies of the game board accordingly. It should also update any visual representation of the game board presented to the user. The server MUST send a full game board update following this message if the game board is not empty. The server should not send this message while a game is in progress. The server MUST send this message at least once, BEFORE sending the first BPOS or BROW message. The client should refrain from responding to this message by requesting a full game board update, since the server is obligated to send an update unless the game board is now empty. V.A.2.c. BPOS This message is sent by the server to inform the client of the current contents of the game board. The client should update its local copy of the board as indicated in the message. The format of this message is: BPOS where and are the coordinates of the board cell concerned, and is a board cell specification. Note that game board coordinates start counting from 0, up to 1 less than the dimension given in a BDIM message. When large portions of the game board have changed, the server SHOULD use the BROW message to update the client instead. Note that BPOS and BROW messages from the server are authoritative of the state of the game board. Clients should update any local copies of the game board and any visual representation of it accordingly upon receiving these updates. The server MUST NOT send this message before sending a BDIM message to the same client. If the client receives a BPOS before it receives a BDIM, it should ignore the message. V.A.2.d. BROW This message is sent by the server to inform the client of the current contents of the game board. The format of this message is: BROW ... where is the number of the row (counting from 0), and each of ... is a board cell specification. Each of the 's represents a cell in row of the game board, in order, from leftmost column to rightmost. The server MUST send the same number of 's as there are columns in the game board, as specified by a previous BDIM message. The server MUST NOT send this message until it has sent a BDIM message to the same client. The client should update any local copies or visual representations of the game board upon receiving this message. V.A.2.e. BEND This message is sent by the server to inform the client that it has finished sending game board updates via BPOS and BROW. THe format of this message is BEND This message is intended to provide hints to the client as to when to actually update the visual representation of the game board to the user. The client may choose to ignore this message and update the game board after every BPOS or BROW message. V.A.2.f. NAME This message is a broadcast message that supplies additional information about a client to other clients. The server should broadcast this message to all existing clients. The format of this message is: NAME

[ ... ] where is a server-assigned identifier for the client;

is 0, 1, or 2; is a one-word nickname for the client; and the optional supplies any additional information about the client. If

is 1 or 2, it refers to the two players playing the current game. If it is 0, it refers to one of the Watcher clients. Note that all Watcher clients will have

= 0; clients receiving this message should not blindly assume there is only one Watcher. Generally, is assigned by the server and and are set by the client. However, the server is free to override any client-supplied or . The assigned by the server should be unique among all existing clients. It is suggested that the server incorporate the network address of the client as part of . Clients are not required to display any information in this message to the user. Clients may choose to completely ignore this message. It is purely for informational purposes. Servers are not required to send any NAME messages. However, it is recommended that the server send a list of all existing clients to a newly-connected client, once it enters Connected State. V.A.2.g. CONN This message is a broadcast message that informs currently connected clients about a newly connected client. The server should broadcast this message to all existing clients. The format of this message is: CONN [ ...] where is a server-assigned identifier for the client, and is an optional informational message. Clients are not required to display any information in this message to the user. Clients may choose to completely ignore this message. It is purely for informational purposes. V.A.2.h. GONE This message is a broadcast message indicating that a particular client has disconnected from the server. The format of this message is: GONE

[] where is a server-assigned identifier for the client;

is 0, 1, or 2; and is a one-word nickname for the client (if any). If

is 1 or 2, it indicates that one of the players currently playing the game has been disconnected, intentionally or otherwise. If

is 0, it indicates that one of the Watcher clients have left. The server MUST send a GONE message if one of the players in the current game has disconnected. However, the server is not obligated to send a GONE message for Watcher clients who left. V.A.2.i. WIN This message indicates that the current game has ended, and has been won by one of the players. The format of this message is: WIN

where

is the player number of the winner. The server MUST send this message to at least both Player clients involved in the current game. The server should also broadcast this message to Watcher clients as well. All Player clients receiving this message MUST switch back to Watcher mode. It is recommended that the server pause after sending this message before starting a new game. V.A.2.j. DRAW This message indicates that the current game has ended, but with neither player as the winner. The format of this message is: DRAW [ ...] where is an optional human-readable message explaining why the game was a draw. The most likely cause is that the game board has become full. The server MUST send this message to at least both Player clients involved in the current game. The server should also broadcast this message to Watcher clients as well. All Player clients receiving this message MUST switch back to Watcher mode. It is recommended that the server pause after sending this message before starting a new game. V.A.2.k. ABRT This message indicates that the current game has been prematurely aborted, possibly due to one of the players disconnecting or resigning, a server problem, or some other reason. The format of this message is: ABRT [ ...] where is an optional human-readable message indicating why the game was aborted. The server MUST send this message to any remaining Player clients involved in the aborted game. The server should also broadcast this message to Watcher clients. All Player clients receiving this message MUST switch back to Watcher mode. It is recommended that the server pause after sending this message before starting a new game. V.A.2.l. CHAT This message is an optional message the server may relay from any of the currently connected clients to all other clients. The format of this message is: CHAT

Where is a server-assigned identifier,

is the player number of the client sending the message, and is the client's chosen nick. Clients receiving this message should display the message to the user; however, clients are not required to implement this feature if they so choose. V.A.2.m. INFO This message is an optional informational message sent by the server. The format of this message is: INFO Where is any informational message. This protocol does not define the syntax of such messages. The client may display the message to the user for informational purposes; however, clients are allowed to completely ignore INFO messages. V.A.3. Non-fatal errors Non-fatal error messages from the server all have the general form: GRR [ ...] where is an error number, and is an optional human-readable error message. Error numbers may be one of the following: 101 Not playing. This error is sent to clients who attempted to make a move while not a player in the current game. 102 Not Your Turn. Sent to Player clients who attempted to move when it's not yet their turn. 103 Bad move attempted. This error is sent in response to a MOVE message which indicated an invalid move. The should explain why it was an invalid move. 104 Finish Playing First. This error is sent in response to a WATCH message while the client is still an active player in the current game. The client should send the RSGN message rather than a WATCH message if it wishes to resign from the current game. 201 Bad nick. The server dislikes the nick the client has requested for itself, and has NOT changed the client's nick. Note that the server is NOT obligated to respond with this error in such cases; it may silently strip away unwanted characters from the requested nick and assigned the result to the client. 802 Minor protocol error: the server could not understand the message the client sent; however, no fatal problems are anticipated, so clients may choose to continue normally. (Yes, "GRR" is a lame pun on "Game eRRor".) V.A.4. Fatal errors Fatal error messages from the server all have the general form: ERR [ ...] where is an error number, and is an optional human-readable error message. Error numbers may be one of the following: 901 Incompatible versions. The server is disconnecting the client because it uses an incompatible game/protocol version. The server should disconnect the client immediately after sending this message. 902 Protocol error. The server received a garbled message from the client which it could not understand. 903 Server is full. The server cannot accomodate any more clients. This is usually caused by too many Watcher clients being connected at once. 911 Internal server error. A (potentially fatal) error has occurred in the server. Clients ought to disconnect. 999 Bad client behaviour. The server is disconnecting the client because it has persisted in abusive behaviour, probably also consistently violating this protocol. V.B. Client Messages V.B.1. NAME This message is sent by the client to request for the server to set information about it which is potentially broadcast to other clients. The format of this message is: NAME [ ...] where is a one-word nickname the client wishes to be known as, and is an optional one-line description about the client. The server may impose arbitrary restrictions on what constitutes a valid . The server may respond with a GRR 201 (bad nick) if it has any objections to the requested nick. However, the server is NOT obligated to do so; it may quietly strip away unwanted characters from and/or alter it so that it conforms to server rules. The server is not obligated to do anything with this message, and is free to completely ignore it. However, if the server does store the information about the client, it should broadcast this fact to all existing clients via the NAME server message. Note that is purely informational; it is not intended to be an identification handle to the server. V.B.2. REQU This message is sent by the client to request a game board update. The format of this message is: REQU Upon receiving this message, the server should update the client with a series of BROW messages containing the full information about the current state of the game board. V.B.3. MOVE This message is sent by the client to make a move in the game. This message should only be sent when the client has received a TURN message carrying its previously-assigned player number. The format of this message is: MOVE where and specify the game board coordinates where the user has decided to make the next move. Clients should not update their local copy of the game board immediately after sending a MOVE message; they should wait for the server's response. The server should send a BPOS message to the client once the move is made. If the move is invalid, the server should send a GRR 103, and should include a human-readable message explaining why the move was invalid. V.B.4. RSGN This message is sent by a Player client when it wishes to resign from the current game. The format of this message is: RSGN Upon receiving this message, the server may either terminate the game with an ABRT message explaining why the game was terminated, or may end the game by having the other Player client win the game (sending WIN messages as appropriate). Client should not send this message unless they are one of the players in the current game. The server MUST NOT terminate the game if this message comes from a client who is not one of the players in the current game. V.B.5. CHAT This message is sent by a client to the server when it wishes to chat with other connected clients. The format of this message is: CHAT Typically, comes from whatever the user typed at the client. This is an optional feature; the server may choose to completely ignore CHAT messages. If the server does implement this feature, it should relay CHAT messages to other connected clients using the server's CHAT message (see V.A.2.k.). V.B.6. NEXT This message is sent by the client to request that the game proceed to the next round. The format of this message is: NEXT The server should wait for both players to send NEXT before starting the next round of the game. This message MUST only be sent after the current game has been finished by a WIN message or an ABRT message from the server. The server should ignore NEXT messages sent in the middle of a game. V.B.7. PLAY This message is sent by the client in response to the server's POW? message. The format of this message is: PLAY This message indicates that the client wishes to participate in the current game. The server should respond with the appropriate PNUM message depending on whether current game still has available slots for players. The server may choose to ignore this message if the client was not sent a POW? message previously. The server may also ignore this message if a previous PNUM message has already been sent to the client, and a POW? message has not been sent to the client since. However, the server MUST respond to at least one PLAY or WATCH message after sending the client a POW? message. Sending this message does not guarantee the client will become a player; the slot may have already been assigned to another client by the time this message reaches the server. Clients MUST wait for a PNUM message before entering player mode. Clients should interpret the lack of response to this message from the server as an implicit indication that the client is to remain in watcher mode. V.B.8. WATCH This message is sent by the client in response to the server's POW? message. The format of this message is: WATCH This message indicates that the client does NOT wish to participate in the current game. The server should respond with a "PNUM W" message to indicate that the client is now a watcher. The server may choose to ignore this message if the client was not sent a POW? message previously. The server may also ignore this message if a previous PNUM message has already been sent to the client, and a POW? message has not been sent to the client since. However, the server MUST respond to at least one PLAY or WATCH message after sending the client a POW? message. Clients should interpret the lack of response to this message from the server as an implicit indication that the client is to remain in watcher mode. V.B.9. QUIT This message is sent by the client to indicate that it is disconnecting. The format of this message is: QUIT Upon receiving this message, the server should close the client connection and free up any resources allocated for the client, including cancelling any game the client might be currently participating in. V.B.10. WHO? This message requests that the server update the client with information about other clients currently connected to the server. The format of this message is: WHO? Upon receiving this message, the server should send a series of NAME messages containing information about currently-connected clients. However, the server may choose to respond with a GRR 802 (not implemented) non-fatal error instead. atom4/net/testserv.cc0100600000175000017500000000110307646250163014421 0ustar hsteohhsteoh/* * Fake Atom-4 server to test server code */ #include #include // prog/lib version #include "game.h" #include "server.h" #define SERV_PORT 4704 int main() { try { int exitflag=0; eventloop mainloop; atom4local game(16,16); server gamesrv(&game, &mainloop, SERV_PORT, server::ALLOW_OBSERVERS); fprintf(stderr, "Server initialized, waiting for connections\n"); mainloop.run(&exitflag); fprintf(stderr, "Server exited\n"); } catch(exception &e) { fprintf(stderr, "Caught exception: %s\n", e.message()); } } atom4/net/Conscript0100600000175000017500000000047407646257454014146 0ustar hsteohhsteoh#!/usr/bin/cons # # Atom-4 networking routines build script. # # $Id: Conscript,v 1.1 2003/04/13 12:59:18 hsteoh Exp hsteoh $ # Import qw( CONS INCDIR LIBDIR ); # Products Install $CONS $LIBDIR, 'libatomnet.a'; Install $CONS $INCDIR, 'net.h', 'server.h'; Library $CONS 'libatomnet.a', 'net.cc', 'server.cc'; atom4/proglib/Construct0100600000175000017500000000272207633755727015036 0ustar hsteohhsteoh#!/usr/bin/cons # # Library build script. Requires cons, in case you haven't clued in yet. # $Id: Construct,v 1.1 2003/03/13 01:15:59 hsteoh Exp hsteoh $ # # Configurable parameters: (run with 'cons $VARNAME=$VALUE ...') # # CC=$binary C compiler to use (default: gcc) # CXX=$binary C++ compiler to use (default: g++) # ADIR=$dir Where to install .a files # INCDIR=$dir Where to install header files # SODIR=$dir Where to install .so files (currently not implemented) # DEBUG=1 Build with debugging symbols # # # I *love* cons. Make is just an idiotic overly-bandaged contraption way past # its lifetime. Everyone should use Cons for all new products! It will save # you countless hours, days, weeks, of endless build frustrations. I swear # I'll never, ever, use Make again. It's about time it died a long overdue # death. # # Export qw( ADIR INCDIR SODIR CONS ); # # Externally configurable settings # $CC = $ARG{CC} || 'gcc'; $CXX = $ARG{CXX} || 'g++'; $ADIR = $ARG{ADIR} || '#lib'; $INCDIR = $ARG{INCDIR} || '#include'; $SODIR = $ARG{SODIR} || '#lib'; # # Internal setup # $DEBUGFLAG = $ARG{DEBUG} ? '-g3' : ''; $CFLAGS = "-pedantic -Wall $DEBUGFLAG"; $CXXFLAGS = "-pedantic -Wall $DEBUGFLAG"; $INCPATHS = "$INCDIR"; $CONS = new cons( CC => $CC, CFLAGS => $CFLAGS, CXX => $CXX, CXXFLAGS => $CXXFLAGS, CPPPATH => $INCPATHS ); Build qw( c++/Conscript c/Conscript ); atom4/proglib/TODO0100600000175000017500000000673307633766723013623 0ustar hsteohhsteoh- stack and list classes should have operator=() !!! * This will be rather messy to implement... as it will require operator=() on all the nodes, which in turn may require operator=() on the contained objects. This is OK for the e-containers; but the o-containers will then have to force contained objects to implement operator=(). - list (perhaps also stack) classes need a cut() operator that allows more efficient destructive list copying/attaching. How about something similar to Perl's splice? - revise existing libs to use it (our libs REALLY need non-b0rked exception handling; error.h is only good for C) - implement error-handling in stack templates!! * must use new exception.* classes. - implement target to build tar.gz archive, with semi-automatic MANIFEST generation, etc.. + implement standardized library error-code passing. This will probably be based on the error_d/error_t strategy using error codes that are unified with their respective error messages. + re-design the interface for the FILE hierarchy. You'll also need to consider how to maximize backward compatibility while fixing flaws in the original design. + implement iterators for stack templates! + look into why *iter++ doesn't compile, in list.h. Possibly due to operator++ return type: maybe we shouldn't specify return type?? ** No, the problem is, operator++() must be overloaded to return the derived class object rather than the base class object. This is NOT automatically done! + implement doubly-linked lists! X port SPLITSRC. Or perhaps re-implement it, since the existing code is too messy. * Forget it. It's not that useful anymore... or at least, if you want it, you should redesign with a less b0rked design! X design a set of basic print-formatting operations, such as typesetting integers, etc., that respects buffer bounds. * Finally the libc people have caught on, and have implemented niceties like snprintf() and vsnprintf(). + re-organize this messy directory! + convert to use Cons + separate C and C++ libs (too messy otherwise). X file.h probably needs re-designing... esp. the read/write interface (though fread/fwrite seems cumbersome, they have better error behaviour when it comes to file size). * the file.* hierarchy has been deprecated. Design is obsolete. X re-design config file module! * will need redesign from scratch; old design is obsolete. X write README file for this dir to describe lib usage. In particular, note that alternative implementations of a module can be selected by specifying the implementation dir in the #include path, eg.: #include #include This mechanism has many flaws, though... we should come up with a better way of managing selection of alternative implementations! * Split up into C/C++ libraries. We'll worry about alternative implementations in the future. At any rate, it should be much easier under Cons. X adapt DBUF module code to work with new error-code passing scheme. * DBUF is obsolete. Probably should adapt idea into a better design. X clean up code for buffered file classes (BFILE module). You need to make it more secure! * the FILE hierarchy is obsolete. + incorporate better exception-handling class from atom4/x + probably need to clean it up a bit before we put it in here + at least need some form of locus field + *could* just implement this as class hierarchy and just have the locus as a convention rather than an enforced rule. atom4/proglib/c/error.h0100600000175000017500000000502106570671416014635 0ustar hsteohhsteoh/* * Standardized error code module. * Header file * --------------------------------------------------------------------------- * DESIGN * * The purpose of this module is to unify error codes and error messages. * * Traditionally, errors are assigned an arbitrary integer value (or possibly * an enumerated value). These codes are unique only within the module that * defines them; when error codes are returned from several different modules * it is difficult to distinguish which error came from where. Also, if the * error code needs to be propagated through several modules, a series of * conversions are required. To obtain a human-readable message, a special * error-to-message mapping function is usually needed. After the error code * has been converted several times, it is difficult to know which mapping * function should be used. * * This module tries to fix these problems. Error codes are defined such that * every error is unique throughout the entire program. Furthermore, an error * code equals the error message, and thus the message can be accessed directly * without any need of locating and using the correct mapping function. * * This is accomplished by defining an error code to be a pointer to the * message describing the error. Since all strings are stored at unique memory * locations, all error codes will be unique. There is not even a need to * manually assign unique error codes across all modules (which is impractical, * especially if library modules are used). Also, the error code itself is * the pointer to the error message: the message associated with the error is * right at your fingertips ready to be accessed. * * IMPLEMENTATION * * By convention, all error codes will have uppercase names, with the first * part of the name containing a prefix that identifies which module the * error is defined in. Errors are defined using the 'error_d' typedef: * error_d STK_CANNOT_POP="Nothing to pop from stack"; * Error codes are stored in variables of type 'error_t', but assigned an * initial value from 'error_d': * error_t e; * e = STK_CANNOT_POP; * printf("Error: %s\n", e); * * By convention, error codes should be declared in the header file of the * module it belongs to, but defined in the implementation files. So, the * header file should contain something like: * extern error_d STK_CANNOT_POP; * extern error_d STK_INTERNAL_ERROR; * ... etc. */ #ifndef ERROR_H #define ERROR_H typedef char error_d[]; typedef char *error_t; #define NO_ERROR ((error_t)0) #endif /* ERROR_H */ atom4/proglib/c/Conscript0100600000175000017500000000040207633755616015226 0ustar hsteohhsteoh# # C library build script # $Id: Conscript,v 1.1 2003/03/13 01:14:49 hsteoh Exp hsteoh $ # Import qw(CONS ADIR INCDIR SODIR); # # Products # Install $CONS $INCDIR, qw( error.h ); # Currently, we don't actually have any linkables in the C library. 1; atom4/proglib/c++/list.h0100600000175000017500000002100107646543450014602 0ustar hsteohhsteoh/* * Simple singly-linked list class & templates * Header file * $Id: list.h,v 1.10 2003/04/14 14:35:03 hsteoh Exp hsteoh $ * ---------------------------------------------------------------------------- * USAGE * * Ordinary users of the class should only use the templates 'elist' and * 'olist'. The other classes are implementation-specific and should not be * used directly unless you're implementing another 'flavour' of list besides * the 'e' and 'o' flavours. * * The difference between these two flavours of lists is the way the contained * objects are stored. * * In the 'e' flavour of the list, implemented as the template 'elist', objects * are passed directly as parameters to methods like append(), prepend(), * insert(), etc.. These functions then use the copy constructor of the object * to make a copy of the object, and stores this copy in the list. Likewise, * when an object is removed from the list, its copy constructor is used to * make a copy which is then returned to the caller. * * In the 'o' flavour of the list, implemented as the template 'olist', the * *pointer* to the object to be stored in the list is passed. No copy * constructor is used; the list simply stores a pointer to the object. When * the object is removed from the list, a pointer to it is returned, again * without any invocation of copy constructors (or some such method). Note, * however, that these object pointers are 'owner' pointers, meaning that when * the pointer is passed to the list, the object from then on is 'owned' by the * list -- the list is responsible for destroying the object if the list * destructor is called; and when the pointer is returned from the list, the * responsibility is passed back to the caller. * * The 'e' flavour is primarily meant for storing atomic types or small objects * whose copy constructors are small and efficient. The 'o' flavour is meant * for storing potentially large objects that you do not want to keep making * copies of just for the sake of moving it in and out of lists. * * Note that the 'o' flavour is NOT a syntactic shortcut to instantiating the * 'e' flavour with a pointer to the object type. The instance elist * will store only pointers to the object, but will not maintain the object * pointed to. An olist also stores obj* pointers, but when an olist * is destructed, it calls the destructor of every object that is still * contained in it. */ #ifndef LIST_H #define LIST_H #include // must be the prog/lib/ version. /* PRIVATE CLASSES * These are not meant to be used directly; normal users should only use * the public templates provided. */ // This provides class _listbase with access to protected members in // _stacknode. class _listnode : public _stacknode { friend class _listbase; friend class _listiter; }; // This class provides class _listbase with access to protected members in // class _stackiter. class _listiter : public _stackiter { friend class _listbase; protected: _listiter(_listnode *node) : _stackiter(node) {} }; // Singly-linked list base class. Built by extending stack base. class _listbase : public _stackbase { protected: _listnode *bottom; // tail of list int count; // count number of elements in list public: _listbase() { bottom=0; count=0; } void clear() { _stackbase::clear(); bottom=0; count=0; } ~_listbase() { count=0; } // (base class automatically calls // _stackbase::clear().) /* Operations */ inline void prepend(_listnode *n) { if (!bottom) bottom=n; // special case when *bottom needs to // be updated. push(n); // prepend()==base class::push(). count++; } inline void append(_listnode *n) { if (bottom) { bottom->next = n; } else { // special case: list is empty push(n); // so prepend=append=push. } bottom = n; n->next = 0; count++; } // Inserts a node *after* the node pointed to by &where. If iterator // is NULL, inserts at head of list. // Be ABSOLUTELY SURE that the iterator actually points to something within // the list! otherwise you'll completely screw up your lists. inline void insert(_listnode *n, _listiter &where) { if (!where.ptr) { prepend(n); // special case: insert at head of list } else { n->next = ((_listnode*)where.ptr)->next; ((_listnode*)where.ptr)->next = n; // attach new node after current node if (((_listnode*)where.ptr)==bottom) bottom=n; // tail of list has moved. count++; } } // Removes the SUCCESSOR of &pred. If &pred is NULL, removes the head // element of the list. // Returns the removed node. The caller is responsible for deallocating // it, etc.. _listnode *remove(_listiter &pred); /* Queries */ inline int num_elem() { return count; } }; /* PRIVATE TEMPLATES */ template class elist; // (forward declaration) template class elistiter; // (forward declaration) template class elistnode : public _listnode { friend class elist; friend class elistiter; type data; elistnode(type d) : data(d) {} ~elistnode() {} }; template class elistiter : public _listiter { friend class elist; protected: elistiter(elistnode *node) : _listiter(node) {} public: elistiter() : _listiter(0) {} type &operator*() { return ((elistnode *)ptr)->data; } elistiter &operator++() { _listiter::operator++(); return *this; } elistiter operator++(int) { elistiter old(*this); _listiter::operator++(); return old; } }; template class olist; // (forward declaration) template class olistiter; // (forward declaration) template class olistnode : public _listnode { friend class olist; friend class olistiter; type *data; olistnode(type *d) : data(d) {} ~olistnode() { if (data) delete data; } }; template class olistiter : public _listiter { friend class olist; protected: olistiter(olistnode *p) : _listiter(p) {} public: olistiter() : _listiter(0) {} type *operator*() { return ((olistnode *)ptr)->data; } olistiter &operator++() { _listiter::operator++(); return *this; } olistiter operator++(int) { olistiter old(*this); _listiter::operator++(); return old; } }; /* PUBLIC TEMPLATES */ template class elist : public _listbase { public: void append(type data) { elistnode *n = new elistnode(data); _listbase::append(n); } void prepend(type data) { elistnode *n = new elistnode(data); _listbase::prepend(n); } void insert(type data, elistiter &where) { elistnode *n = new elistnode(data); _listbase::insert(n, where); } // If the node to be removed doesn't exist, the default value for // the given type is returned (for atomic types, this may be a junk value). // The safest bet is still to check the iterator yourself before calling // this function. type remove(elistiter &pred) { elistnode *p; type data; p = (elistnode *)_listbase::remove(pred); if (p) { data=p->data; delete p; // deallocate node } return data; } elistiter headp() { return elistiter((elistnode *)_top); } elistiter tailp() { return elistiter((elistnode *)bottom); } }; template class olist : public _listbase { public: void append(type *ptr) { olistnode *n = new olistnode(ptr); _listbase::append(n); } void prepend(type *ptr) { olistnode *n = new olistnode(ptr); _listbase::prepend(n); } void insert(type *ptr, olistiter &where) { olistnode *n = new olistnode(ptr); _listbase::insert(n, where); } // Returns NULL if the node to be removed doesn't exist. // An owner pointer to the removed object is returned: ie. the caller is // responsible for destructing this object unless the pointer is NULL. type *remove(olistiter &pred) { olistnode *p; type *d=0; p = (olistnode *)_listbase::remove(pred); if (p) { d = p->data; p->data = 0; // so that olistnode dtor doesn't // delete data object. delete p; // discard list node } return d; // return retrieved data } olistiter headp() { return olistiter((olistnode *)_top); } olistiter tailp() { return olistiter((olistnode *)bottom); } }; #endif // LIST_H atom4/proglib/c++/dlist.h0100600000175000017500000002213507646122401014744 0ustar hsteohhsteoh/* * Doubly-linked list classes and templates * Header file * $Id: dlist.h,v 1.2 2003/04/12 23:44:28 hsteoh Exp hsteoh $ * ---------------------------------------------------------------------------- * DESIGN * * The implementation of doubly-linked list is based on a careful design of * how to cleanly implement C++ containers (ref stack.h). This design basically * separates the `essential' aspect of the container (the base classes) from * the user-customized aspect (the templates which allow arbitrary data types * to be stored), called `flavors' of the container. The base classes should * ONLY be used for either making new `flavors' (ie. templates) or for deriving * other containers by extending this container. The user should ONLY use * the `flavors' (templates). * * Naming conventions: each flavor has a letter associated with it (currently * `e' and `o'). The template of that flavor is named by prefixing the flavor * letter to the base name of the container (`dlist' in this case). Therefore, * the two flavors provided are `edlist' and `odlist'. (This convention applies * to all containers based on the aforementioned container design, which are, * as of the time of this writing, `list' and `stack'. So the flavors of those * containers would be `elist', `olist', and `estack', `ostack', respectively.) * * The `e' flavor (`e' stands for `embedded object') containers store objects * by making a *copy* (using the copy constructor or operator=() of the object) * of the object in the internal node structures. When an object is taken out * of the container, a *copy* is made and returned, and the original is * destroyed. This flavor is useful for scalar variables and small classes * which has insignificant copying overhead. * * The `o' flavor (`o' stands for `owner pointer') containers store objects * by storing a *pointer* to the object to be contained, and returning this * pointer when the object is removed from the container. No copies are made. * However, this pointer is an "owner" pointer -- meaning that this pointer is * considered the `master pointer' of the object -- if the object is still * inside the container when the container destructs, the destructor will * *deallocate* the object via the pointer. The `ownership' of the object * is transferred to the container when the pointer is passed into it; and the * ownership of the object is passed back to the caller when the object is * removed from the container. While the container owns the object, it is * responsible for proper management of the memory associated with the object; * hence the object is destructed when the container destructs, unless the * object is removed from the container first. All this means that the `o' * flavor containers may ONLY store dynamically-allocated objects. * * Note that the `o' flavor is absolutely NOT equivalent to an `e' flavor * with a pointer to the object -- an `e' flavor container may only store * `reference pointers', not owner pointers: i.e., the `e' flavor container * never assumes ownership of the object the pointer points to, and when the * container destructs, any object pointed to by the container is NOT * deallocated. (In this respect, the stored pointers behave exactly as * scalars). */ #ifndef DLIST_H #define DLIST_H /* CLASSES */ class _dlistnode { friend class _dlistbase; friend class _dlistiter; protected: _dlistnode *prev; // [own] _dlistnode *next; // [own] _dlistnode() : prev(0), next(0) {} public: virtual ~_dlistnode(); // destruct derived classes properly }; class _dlistiter { friend class _dlistbase; protected: _dlistnode *ptr; // [ref] _dlistiter(_dlistnode *node) : ptr(node) {} public: // operator int() returns a boolean value indicating whether the // iterator is pointing to a valid dlist node or not. When operator++() // steps beyond the end of the list, operator int() will return false; so // this is useful for stopping loops involving iterators. inline operator int() { return (ptr!=0); } inline void invalidate() { ptr=0; } // useful when NULL stands for // something (eg. insert at list head) inline _dlistiter &operator-- () { if (ptr) ptr=ptr->prev; return *this; } inline _dlistiter &operator++ () { if (ptr) ptr=ptr->next; return *this; } }; class _dlistbase { protected: _dlistnode *head, *tail; // [own][own] int count; public: _dlistbase() : head(0), tail(0), count(0) {} void clear(); ~_dlistbase() { clear(); } void operator=(_dlistbase &l); // Note: insert() takes owner ptr to n. // Note: set succ=NULL to append to list. Note: *n MUST initially have // next and prev set to NULL, otherwise this code will malfunction. void insert(_dlistnode *n, _dlistnode *succ); // Note: remove() returns owner ptr. _dlistnode *remove(_dlistnode *n); inline void append(_dlistnode *n) { insert(n, 0); } inline void prepend(_dlistnode *n) { insert(n, head); } inline int empty() { return head==0; } inline int num_elem() { return count; } }; /* PRIVATE TEMPLATES */ // forward declarations template class edlist; template class edlistiter; template class edlistnode : public _dlistnode { friend class edlist; friend class edlistiter; type data; edlistnode(type d) : data(d) {} ~edlistnode() {} }; template class odlist; template class odlistiter; template class odlistnode : public _dlistnode { friend class odlist; friend class odlistiter; type *ptr; // owner ptr to contained object odlistnode(type *p) : ptr(p) {} ~odlistnode() { if (ptr) delete ptr; } }; /* PUBLIC TEMPLATES */ // Template for edlist iterator. template class edlistiter : public _dlistiter { friend class edlist; protected: edlistiter(edlistnode *node) : _dlistiter(node) {} public: edlistiter() : _dlistiter(0) {} inline type &operator*() { return ((edlistnode *)ptr)->data; } inline edlistiter &operator++() { _dlistiter::operator++(); return *this; } inline edlistiter operator++(int) { edlistiter old(*this); _dlistiter::operator++(); return old; } inline edlistiter &operator--() { _dlistiter::operator--(); return *this; } inline edlistiter operator--(int) { edlistiter old(*this); _dlistiter::operator--(); return old; } }; template class edlist : public _dlistbase { public: edlist() {} ~edlist() {} inline void append(type data) { edlistnode *n = new edlistnode(data); _dlistbase::append(n); } inline void prepend(type data) { edlistnode *n = new edlistnode(data); _dlistbase::prepend(n); } inline void insert(type data, edlistiter &succ) { edlistnode *n = new edlistnode(data); _dlistbase::insert(n, succ.ptr); } // Note: this will malfunction if n==NULL. type remove(edlistiter n) { edlistnode *node; type data; // disconnect node from list node = (edlistnode *)_dlistbase::remove(n.ptr); // return contained value data=node->data; delete node; return data; } edlistiter headp() { return edlistiter((edlistnode*)head); } edlistiter tailp() { return edlistiter((edlistnode*)tail); } }; // Template for odlist iterator. template class odlistiter : public _dlistiter { friend class odlist; protected: odlistiter(odlistnode *node) : _dlistiter(node) {} public: odlistiter() : _dlistiter(0) {} inline type *operator*() { return ((odlistnode *)ptr)->ptr; } inline odlistiter &operator++() { _dlistiter::operator++(); return *this; } inline odlistiter operator++(int) { odlistiter old(*this); _dlistiter::operator++(); return old; } inline odlistiter &operator--() { _dlistiter::operator--(); return *this; } inline odlistiter operator--(int) { odlistiter old(*this); _dlistiter::operator--(); return old; } }; template class odlist : public _dlistbase { public: odlist() {} ~odlist() {} inline void append(type *data) { odlistnode *n = new odlistnode(data); _dlistbase::append(n); } inline void prepend(type *data) { odlistnode *n = new odlistnode(data); _dlistbase::prepend(n); } inline void insert(type *data, odlistiter &succ) { odlistnode *n = new odlistnode(data); _dlistbase::insert(n, succ.ptr); } // Note: this will malfunction if n==NULL. type *remove(odlistiter n) { odlistnode *node; type *data; // disconnect node from list node = (odlistnode *)_dlistbase::remove(n.ptr); // return contained value data=node->ptr; node->ptr=0; // else dtor will trash returned data! delete node; return data; } odlistiter headp() { return odlistiter((odlistnode*)head); } odlistiter tailp() { return odlistiter((odlistnode*)tail); } }; #endif // DLIST_H atom4/proglib/c++/stack.h0100600000175000017500000001652506756607013014750 0ustar hsteohhsteoh/* * Simple stack class & templates * Header file * $Id: stack.h,v 1.8 1998/11/24 00:41:16 hsteoh Exp hsteoh $ * --------------------------------------------------------------------------- * DESIGN * * The stack implementation found here is based on a careful design of * how to cleanly implement C++ containers. Unfortunately, due to a hard drive * crash, the original document is lost. So the design as it applies to this * stack implementation will be repeated here. * * Basically, the stack as the user sees it has two distinct 'flavours': * the 'e' and 'o' flavours. In the original document, three flavours, 'e', * 'p' and 'op' were described. The 'p' flavour is now deemed unnecessary; and * the 'op' flavour has been renamed to 'o'. These are templates that should * be used by ordinary users of the stack. The template names are 'estack' and * 'ostack', respectively. * estack is a stack that uses the copy constructor and * operator=() of the objects that it stores. That is to * say, objects are 'embedded' in the stack: pushing an * object involves making a copy of the passed object * and popping an object returns a copy of the contained * object. * ostack is a stack that uses 'owner' pointers to the contained * objects. This means that if the stack is destroyed, * all contained objects will be destructed. Once the * object is pushed onto the stack, the stack becomes the * 'owner' of the object: the caller should no longer * deallocate the object via the pointer. Popping an * object off the stack restores ownership to the caller * -- ie., after popping, the caller is responsible for * deallocating the object. * These templates are implemented on top of a base-level non-template stack * implementation. This base implementation should NOT be used directly by * normal users -- normal users should only peruse the templates. The base * implementation is available only for developers who want to implement their * own 'flavour' of stack templates. * * The base-level stack class is also for use when developing containers * that extend the basic stack implementation here. It is intended that the * class hierarchy of containers be amongst the base-level classes, and that * the user should see only the 'e' and 'o' templates built on each of the * classes. */ #ifndef STACK_H #define STACK_H /* CLASSES */ // Internal class to be used only by stack implementation. // Programmer's notes: // - the destructor does NOT deallocate subsequent nodes in the linked list! // You need class _stackbase::clear() for correct deallocation of the list. class _stacknode { friend class _stackbase; friend class _stackiter; protected: _stacknode *next; _stacknode() : next(0) {} public: virtual ~_stacknode(); // (destruct derived classes properly) }; // Internal class to be used only by stack implementation. class _stackiter { friend class _stackbase; protected: _stacknode *ptr; _stackiter(_stacknode *node) : ptr(node) {} public: // operator int() returns a boolean value indicating whether the // iterator is pointing to a valid stack node or not. When operator++() // steps beyond the end of the list, operator int() will return false; so // this is useful for stopping loops involving iterators. inline operator int() { return (ptr!=0); } inline void invalidate() { ptr=0; } // useful when NULL stands for // something (eg. insert at list head) inline _stackiter &operator++ () { if (ptr) ptr=(_stacknode *)ptr->next; return *this; } }; // Internal class to be used only by stack templates. class _stackbase { protected: _stacknode *_top; public: _stackbase() { _top=0; } void clear(); ~_stackbase() { clear(); } void operator=(_stackbase &s); inline void push(_stacknode *n) { n->next = _top; _top = n; } inline _stacknode *pop() { _stacknode *n=_top; if (_top) _top = _top->next; return n; } inline int empty() { return (_top==0); } }; /* PRIVATE TEMPLATES */ // Private template used by template class estack. template class estack; // (required forward declaration) template class estackiter; // (ditto) template class estacknode : public _stacknode { friend class estack; friend class estackiter; type data; // embedded object constructed using // copy constructor. estacknode(type d) : data(d) {} ~estacknode() {} }; // Private template used by template class opstack. template class ostack; // (required forward declaration) template class ostackiter; // (ditto) template class ostacknode : public _stacknode { friend class ostack; friend class ostackiter; type *ptr; // owner pointer to contained object ostacknode(type *p) : ptr(p) {} ~ostacknode() { if (ptr) delete ptr; } }; /* PUBLIC TEMPLATES */ // Iterator for estack template. template class estackiter : public _stackiter { friend class estack; protected: estackiter(estacknode *node) : _stackiter(node) {} public: estackiter() : _stackiter(0) {} inline type &operator*() { return ((estacknode *)ptr)->data; } inline estackiter &operator++() { _stackiter::operator++(); return *this; } inline estackiter operator++(int) { estackiter old(*this); _stackiter::operator++(); return old; } }; // 'Embedded' stack template. template class estack : public _stackbase { public: estack() {} ~estack() {} inline void push(type d); inline type pop(); inline estackiter top(); }; // Iterator for ostack template. template class ostackiter : public _stackiter { friend class ostack; protected: ostackiter(ostacknode *p) : _stackiter(p) {} public: ostackiter() : _stackiter(0) {} inline type *operator*() { return ((ostacknode *)ptr)->ptr; } inline ostackiter &operator++() { _stackiter::operator++(); return *this; } inline ostackiter operator++(int) { ostackiter old(*this); _stackiter::operator++(); return old; } }; // 'Owner-pointer' stack template. template class ostack : public _stackbase { public: ostack() {} ~ostack() {} inline void push(type *d); inline type *pop(); inline ostackiter top(); }; /* TEMPLATE MEMBER DEFINITIONS */ template void estack::push(type d) { estacknode *n; n = new estacknode(d); _stackbase::push(n); } template type estack::pop() { estacknode *n; n = (estacknode *)_stackbase::pop(); type d(n->data); // use copy constructor to construct // return data value. delete n; return d; } template estackiter estack::top() { return estackiter((estacknode *)_top); } template void ostack::push(type *d) { ostacknode *n; n = new ostacknode(d); _stackbase::push(n); } template type *ostack::pop() { ostacknode *n; type *d=0; n = (ostacknode *)_stackbase::pop(); if (n) { d = n->ptr; n->ptr = 0; // (so that destructor won't attempt // to deallocate returned object) delete n; } return d; } template ostackiter ostack::top() { return ostackiter((ostacknode *)_top); } #endif // STACK_H atom4/proglib/c++/dlist.cc0100600000175000017500000000221107633755513015107 0ustar hsteohhsteoh/* * Doubly-linked list classes and templates * Implementation file * $Id: dlist.cc,v 1.1 2003/03/13 01:13:41 hsteoh Exp hsteoh $ */ #include /* FUNCTION DEFINITIONS */ // Note: this does NOT recursively destruct the linked list chain. The // list class is responsible for that. _dlistnode::~_dlistnode() {} void _dlistbase::clear() { _dlistnode *n, *next; for (n=head; n; n=next) { next = n->next; delete n; } head = tail = 0; count = 0; } void _dlistbase::insert(_dlistnode *n, _dlistnode *succ) { // Link to new successor n->next = succ; if (succ) { n->prev = succ->prev; succ->prev = n; } else { n->prev = tail; tail = n; } // Link to new predecessor if (n->prev) { n->prev->next = n; } else { head = n; } count++; // add new node to count } _dlistnode *_dlistbase::remove(_dlistnode *n) { if (n->prev) { n->prev->next = n->next; } else { head = n->next; } if (n->next) { n->next->prev = n->prev; } else { tail = n->prev; } n->next = n->prev = 0; // clean up removed node (just in case) count--; // update node count return n; } atom4/proglib/c++/list.cc0100600000175000017500000000151507646367361014755 0ustar hsteohhsteoh/* * Simple singly-linked list class & templates * Implementation file * $Id: list.cc,v 1.4 1998/11/24 01:01:36 hsteoh Exp hsteoh $ */ #include // prog/lib/ version /* FUNCTION DEFINITIONS */ _listnode *_listbase::remove(_listiter &pred) { _listnode *p; if (pred.ptr) { p = (_listnode *)((_listnode *)pred.ptr)->next; if (p) { // do nothing if there is no successor ((_listnode *)pred.ptr)->next = p->next; // re-link rest of list to pred. if (!p->next) // special case: tail node removed bottom=((_listnode *)pred.ptr); count--; } } else { // remove head of list p = (_listnode *)pop(); // pop off top element of list if (p) { if (!_top) bottom=0; // last node in list removed count--; } } // endif pred.ptr return p; } atom4/proglib/c++/stack.cc0100600000175000017500000000064006756002515015071 0ustar hsteohhsteoh/* * Simple stack class & templates * Implementation file * $Id: stack.cc,v 1.6 1999/08/16 12:33:59 hsteoh Exp hsteoh $ */ #include /* FUNCTION DEFINITIONS */ _stacknode::~_stacknode() {} void _stackbase::clear() { _stacknode *node, *next=0; for (node=_top; node; node=next) { // Linked-list deallocator next = node->next; delete node; } _top=0; // stack has no nodes now!! } atom4/proglib/c++/Conscript0100600000175000017500000000054307641130060015335 0ustar hsteohhsteoh# # C++ library build script # $Id: Conscript,v 1.4 2003/03/28 20:27:17 hsteoh Exp hsteoh $ # Import qw(CONS ADIR INCDIR SODIR); # # Products # Install $CONS $INCDIR, qw( dlist.h exception.h list.h stack.h txtutil.h ); Install $CONS $ADIR, 'libt++.a'; Library $CONS 'libt++.a', qw( dlist.cc exception.cc list.cc stack.cc txtutil.cc ); 1; atom4/proglib/c++/exception.cc0100600000175000017500000000366207633762343016000 0ustar hsteohhsteoh/* * Simple C++ exception classes * Implementation file * * $Id: exception.cc,v 1.4 2003/03/13 01:54:31 hsteoh Exp hsteoh $ */ #include #include #include #include #include "exception.h" // This is a fatal, irrecoverable error, because there is no guarantee we // can still throw an exception. void exception::out_of_memory() { fprintf(stderr, "Fatal error: out of memory in exception ctor\n"); abort(); // fatal: cannot create exception } void exception::varinit(char *fmt, va_list args) { size_t bufsize, msglen; // Initial attempt to format message string bufsize = 3*strlen(fmt)/2; // guesstimate of required length msg = new char[bufsize]; if (!msg) out_of_memory(); // die horribly msglen=vsnprintf(msg, bufsize, fmt, args); // If we didn't get the complete message, reallocate a buffer that is large // enough (vsnprintf() returns the number of chars that would've been written // excluding '\0', although it won't write more than bufsize bytes including // the '\0'.) if (msglen+1 > bufsize) { bufsize = msglen+1; delete msg; // reallocate buffer msg = new char[bufsize]; if (!msg) out_of_memory(); // die horribly vsnprintf(msg, bufsize, fmt, args); } } exception::exception(char *error_msg, ...) : is_variable(*error_msg=='@') { if (!is_variable) { // Static exception msg = error_msg; } else { // Variable exception va_list args; va_start(args, error_msg); varinit(error_msg+1, args); // (skip the '@') va_end(args); } } exception::exception(const exception &e) : is_variable(e.is_variable) { if (is_variable) { msg = new char[strlen(e.msg)+1]; if (!msg) out_of_memory(); // no memory: die horribly strcpy(msg, e.msg); } else { msg = e.msg; // static message; just copy pointer } } exception::~exception() { if (is_variable) delete msg; } char *exception::message() { return msg; } atom4/proglib/c++/exception.h0100600000175000017500000000425507633767153015645 0ustar hsteohhsteoh/* * Simple C++ exception classes * Header file * * $Id: exception.h,v 1.4 2003/03/13 02:34:40 hsteoh Exp hsteoh $ * --------------------------------------------------------------------------- * USAGE NOTES * * These classes are intended to be used in a similar way to prog/lib's * error.h typedefs. Except that since we can use C++ classes, we have more * flexibility here. * * * CONVENTIONS * * Especially for library subsystems, each subsystem ought to derive their * own exception class and use that to throw exceptions. This will allow * applications to implement simple exception locus detection by catching * the appropriate base class. */ #ifndef EXCEPTION_H #define EXCEPTION_H #include // USAGE NOTES: // - there are two subtypes of exceptions: one based on a static string, and // the other based on a dynamic (variable) string. // - static exceptions should pass only a single argument, the error message. // - variable exceptions must have '@' as their first character; the char* // argument is then treated as a printf format string, with optional // arguments that follow. // - WARNING: variable exceptions keeps an internal pointer to a formatted // string buffer which is dynamically allocated. For this reason, variable // exceptions should NOT be used for critical exceptions like out of memory // errors, where there is no guarantee the ctor won't fail. class exception { int is_variable; char *msg; void out_of_memory(); // does not return void varinit(char *msg, va_list args); protected: // NOTE: the dummy argument is a dirty, cruel hack necessary because the // stdarg in glibc defines va_list as char*, and the bogonous C++ compiler // (g++) does not know how to find a next-better ctor match when this one // is protected. It's not my fault! Derived exception classes will just // have to cope. exception(int dummy, char *error_msg, va_list args) : is_variable(*error_msg=='@') { if (is_variable) varinit(error_msg+1, args); else msg=error_msg; } public: exception(char *error_msg, ...); exception(const exception &e); virtual ~exception(); virtual char *message(); }; #endif // EXCEPTION_H atom4/proglib/c++/txtutil.h0100600000175000017500000000337307641127517015355 0ustar hsteohhsteoh/* * Generic text formatting utilities * Header file * --------------------------------------------------------------------------- * $Id: txtutil.h,v 1.1 2002/03/23 19:53:39 hsteoh Exp hsteoh $ */ #ifndef TXTUTIL_H #define TXTUTIL_H #include // MUST be prog/lib/ version! /* CLASSES */ // class text: represents a list of lines. This class is intended to represent // formatted text; it has the default (and currently, the only) strategy of // word-wrapping, left-justification. // Programmer's notes: // - eventually, ctor should provide parms to select which formatting strategy // should be used. class text { int width; // max width olist lines; // the actual lines char *line; // current line int x; // current xcoor (for typesetting) int need_space; // need interword space for next word? // (MUST be either 0 or 1) char *make_line(); // create new line void dumb_wrap(char *word, int len); // dumb character-wrapping for words // that don't even fit on a line. public: text(int max_wd, int startx=0) : width(max_wd), line(0), x(startx) {} ~text() {} void add_word(char *start, int len); // appends a word into the buffer. // Words that don't fit on the current // line are wrapped. void add_string(char *string); // parses the string into words and // inserts words into buffer. void add_lines(char *string); // same as add_string() but respects // linebreaks. void linebreak(); // force a linebreak on current line int maxwidth() { return width; } int numlines() { return lines.num_elem(); } olistiter first_line() { return lines.headp(); } }; /* FUNCTION PROTOTYPES */ char *eat_spaces(char *s); // standard whitespace skipping func #endif // TXTUTIL_H atom4/proglib/c++/txtutil.cc0100600000175000017500000000630307641127517015507 0ustar hsteohhsteoh/* * Generic text formatting utilities * Implementation file * --------------------------------------------------------------------------- * $Id: txtutil.cc,v 1.2 2002/03/28 15:16:43 hsteoh Exp hsteoh $ */ #include #include "txtutil.h" /* FUNCTION DEFINITIONS */ char *text::make_line() { line = new char[width+1]; // create new line lines.append(line); // add to existing text *line = '\0'; // (in case someone reads the line // before anything is written to it) need_space=0; // beginning of line: don't need space x=0; // restart at column 0 return line; } // Just wrap to width, disregarding word boundaries. This is a last resort to // typeset words which are longer than the line width. void text::dumb_wrap(char *word, int len) { if (x+need_space+1 <= width) { if (need_space) { *line++ = ' '; // insert space after previous word x++; } } else { line = make_line(); // start filling in word at next line } while (len--) { // simply copy word into line... *line++ = *word++; x++; // until it doesn't fit any more if (len<1) need_space=1; // end of word: need space before next // word (unless there's a linebreak) if (x>=width) { // then fit it on next line *line='\0'; line = make_line(); } } } void text::add_word(char *start, int len) { if (!line) line = make_line(); // create new line if (x+len+need_space <= width) { // does text fit into current line? if (need_space) { // yes *line++ = ' '; // insert interword space x++; // (count the space as well!) } // (fall through to word insertion code) } else if (len<=width) { // need to wrap word line = make_line(); // make a new line // (fall through to word insertion code) } else { // word doesn't even fit on line! dumb_wrap(start, len); return; // (abort) } // Append word to line x += len; // new column position while (len--) { *line++ = *start++; } *line = '\0'; // terminate line (in case someone // accesses the line before next line // break is encountered) need_space=1; // now need space before next word } void text::add_string(char *str) { char *word; while (*str) { str = eat_spaces(str); // eat spaces at beginning of line // scan word (first word has slightly different processing) word=str; while (*str && !isspace(*str)) str++; add_word(word, str-word); // append word to text buffer } // endwhile(*str) } void text::add_lines(char *str) { char *word; if (!line) line = make_line(); // otherwise initial linebreaks won't // work properly. while (*str) { // Eat spaces at start of line, but respect newlines while (*str && isspace(*str) && *str!='\n') str++; if (*str == '\n') { linebreak(); // force a linebreak str++; // eat linebreak } else { // scan word word = str; while (*str && !isspace(*str)) str++; add_word(word, str-word); // append word to buffer } } // endwhile(*str) } void text::linebreak() { line = make_line(); } char *eat_spaces(char *s) { while (*s && isspace(*s)) s++; return s; }