Toolkit
The Ace of Penguins Toolkit allows you to create your own solitaire
games pretty easily. For example, I wrote the first version of
golf in about ten minutes (the toolkit was already done at
that point). My wife watched, and played it when I was done. I later
added more bells and whistles, but the basic game only took ten
minutes.
There are some utilities that come with the toolkit that you'll
need to be familiar with in order to build your game. These tools
allow you to integrate pictures and online help into your program,
much like the Windows(tm) resource compiler. Graphics are created in
the standard GIF format and integrated into your program with the
gif2pack program. Help is written as a limited HTML file and
integrated with text2c. The toolkit has functions that
access these resources when the program needs them.
All Ace programs use the
PNG library and ZLib. Your
system may have them installed, otherwise you'll need to download and
install them.
The library consists of three main parts: A collection of standard
images (including, of course, the deck of cards), functionality to
maintain and manipulate the "table" and interface with X, and a set of
functions to create and manipulate "stacks" of cards.
Getting Started
Your source file must inlude cards.h from wherever you installed
it. In my case, I use #include "cards.h" and let the
Makefile worry about how to tell gcc where to
include it from.
Your program should, in main(), call three functions:
init_ace(argc, argv);
init_table(width, height);
table_loop();
Here, argc and argv are the parameters passed to
main() and width and height are whatever
width and height your window ("table") should be. You'll probably
want to calculate the initial size of the table based on the sizes of
the cards and margins (they're defined in cards.h). The
init_*() calls do everything needed to get the X interface
running. The table_loop() call passes control over to the
Ace library, and does not return. Instead, the Ace library manages
the X interface, and calls functions you define when user interaction
occurs (see the events section below).
Makefiles and Linking
You'll need to add -lpng -lz -lX11 to the end of your link line
in your Makefile. You'll also need to link the Ace library,
which for me means adding ../lib/cardlib.a but you'll need to
do whatever is appropriate for where you installed the library. For
example, if you copied cardlib.a to
/usr/lib/libcards.a then you'd add -lcards instead.
Note that the card library gets linked before the X libraries.
Example:
sol : sol.o sol-help.o sol-img.o
$(CC) $(CFLAGS) -o $@ $^ -lcards -lpng -lz -lX11
Note in the example above that your game will probably consist of at
least three files. First is the game itself (sol.o in this
example). Next is the help file (sol-help.o), which started
as HTML and was converted to C by text2c. Last is the image
library (sol-img.o) which is generated from your pngs by
make-imglib. To build these, you'll need rules like this:
%-help.c : %.html ../lib/text2c
../lib/text2c $*_help < $< > $@
%-img.c : %.png
../lib/make-imglib -i $(srcdir) -n appimglib - $@ $<
Of course, you'll need to adjust these for your situation.
Events
There are a number of functions that you may define in your program to
add functionality. Each of these is called when some event happens,
such as a mouse click or exposure. Here are the events you may define,
with their prototypes:
- void init();
- This function is called once just after your window appears on
the screen. You should use this function to set up the pitures,
stacks, and other data you need to initialize once per program run.
What I do is define a second function called start_over() that resets
the game itself, and have init call start_over as the last thing it
does. That way, I can call start_over from other places also.
- void redraw()
- This function is called whenever part of the X window is
"exposed" and needs to be redrawn. The computer doesn't remember what
was there, it needs to draw it from scratch. Note that the
put_picture functions are designed to work with this event, so that
they will automatically optimize themselves. An X clipping region is
also set up for you. The best way to deal with redraw events is to
redraw the entire window and let the Ace and X libraries worry about
limiting you to the parts that were actually exposed.
- void resize(int width, int height)
- If you provide this function, your game becomes resizable, and
you will be responsible for repositioning all the images and stacks
according to the new size. You will probably need to call
stack_set_card_size (and read back the actual sizes with
stack_get_card_size and stack_get_fans).
- void key(int k, int x, int y)
- This function is called each time the user presses a key. If the
key they pressed is a regular key with an ASCII equivalent (like `h'),
the ASCII code is passed as k, otherwise the keyc ode (like
KEY_F(1) or KEY_UP) is passed. The location of the pointer is passed
as x and y. If you do not define this function, a default is provided
which exits when the user presses ESC or Ctrl-C. Your routine should
do a similar check to remain consistent with other Ace programs.
- void click(int x, int y, int b)
- This function is called whenever the user presses a mouse button.
the primary button is number 1. For maximum compatibility, you should
only check to see if the button is 1 or greater than 1, since the
numbering of buttons isn't guaranteed to be standard.
- void double_click(int x, int y, int b)
- This function is called when the user double-clicks the mouse.
Note that there will be a click() call for the first click but not for
the second. If you do not define this function, click() is called for
every click.
- void drag(int x, int y, int b)
- This function is called when the user moves the pointer while
holding the button down.
- void drop(int x, int y, int b)
- This function is called when the user releases the button.
Table Functions
- CARD_WIDTH, CARD_HEIGHT, CARD_MARGIN
- These macros define the default size of the cards, and the amount
of space you should allow for between cards and around the edge of the
window. By using these macros, your program will have the same style
as other programs written with this library.
- CARD_FAN_DOWN, CARD_FAN_RIGHT,
CARD_FAN_TBDOWN, CARD_FAN_TBRIGHT
- These macros define the default offsets to be used when a stack
of cards is "fanned" in a given direction (only down and right are
supported). The TB versions are for a "tiny bit" of fanning;
just enough to hint at the value of the card, whereas the others
offset them enough to clearly see the value of the card.
- void init_ace(int argc, char **argv)
- This function initlizes the Ace library. It processes all
command line options and connects to the display. After calling this,
the global variables display_width and
display_height are valid, and table_width and
table_height are non-zero if the user requested a specific
size on the command line. If you need to see a command line option,
create an OptionDesc list for your game (see cards.h for details).
- void init_table(int argc, char **argv, int
table_width, int table_height)
- This function sets up the interface to X and creates the initial
window. This function must be called exactly once from
main().
- void table_loop()
- main() must call this function as the last thing it
does. This function will never return. Once called, the library
starts managing the interaction between the user (via X) and the
program (via events).
- Picture *get_picture(char *name)
- This function loads one of the images that you added to your
program with the make-imglib program (either the
application-specific ones, or the standard ones). This function
searches one table prefixed by imglib (the standard set) and
one prefixed by appimglib (the app-specific set). Note that
you may call this function at any time, even before
init_table(), since the actual conversion of the image to
native X format is deferred until it is needed. The format of the
Picture type is as follows:
- int w, h; - the size of the image
Note that images may have a non-rectangular shape. This is
automatically handled by the library.
- void put_picture(Picture *picture, int dx, int dy,
int x, int y, int w, int h)
- This puts a picture onto the screen. This should only be called
by an event function, since it (like most of these types of functions)
call X routines. The picture is one as returned by
get_picture. The dx,dy tells where the upper left
pixel of the picture should be placed on the window (regardless of
what sub-part of the picture you are putting). The x,y and
w,h indicate a sub-part of the picture to be shown. In most
cases, x,y will be zero and w,h will be the actual
size of the picture. Note that, during a redraw event, the
library knows what part of the window actually needs to be redrawn,
and may not draw your picture (or only draw part of it) if it's not
needed to redraw that part of the picture.
- void put_picture_inverted(Picture *picture, int dx, int dy,
int x, int y, int w, int h)
- Same as put_picture, except that the picture is inverted
before putting it. Some things to note: On 24-bit displays, each RGB
component is separately inverted, but on other displays "inverting"
means only swapping black and white pixels. Plan your pictures
accordingly. Also, there is only one inversion cache, so if you have
multiple inverted images, performance will be degraded.
- void set_centered_pic(Picture *picture)
- This allows the program to tell the library to maintain a single
picture that is displayed centered on the window. This is used by the
splash, win, and lose pictures. Pass a NULL pointer to
remove the picture.
- Picture *get_centered_pic()
- This returns the current centered picture, or NULL if
none. My programs use this in the click and key
events to automatically remove the splash picture on the first event,
and to detect when the user clicks on a win or lose picture.
- void clip(int x, int y, int w, int h)
- This changes the clipping rectangle. Most programs won't need
this, since it's set appropriately by the library during exposures.
- void clear(int x, int y, int w, int h)
- This clears (fills with the table color) a given section of the
window.
- void invalidate(int x, int y, int w, int h)
- Forces the library to redraw part of the table. This is different
than just calling redraw() because it sets the clipping info,
clears the area, calls redraw, and handles the centered picture. This
is the preferred way of causing part of the screen to be redrawn.
Your program should alter the information about the layout, then
invalidate the area and let redraw() update the screen.
- void invalidate_nc(int x, int y, int w, int h)
- Same, except that the area is not cleared first. This is an
optimization for the case where you're adding an image, and you don't
want the screen to flash the background color.
- void invalidate_exposure(int ox, int oy, int ow, int oh,
int nx, int ny, int nw, int nh)
- This is a specialty function for the case where you're moving
something from one point to another. Pass the old and new regions,
and it will invalidate the portion of the old region that isn't
covered by the new region. You'd then use invalidate to
update the new region (or just put_picture). By doing it
this way, you can avoid flickering.
- void flush()
- This forces all drawing operations to be pushed to the server
immediately, rather than cached. You should use this between
iterations of an animation to ensure that the server is updating the
screen at a steady pace.
- void beep()
- It makes a beep. Go figure.
- void text(char *s, int x, int y)
- This draws the given string at the given coordinates on the
window. Note that the coordinates are the lower left corner of the
region the text occupies, not the starting baseline point. The font
can be assumed to be a monospaced font, with each character occupying
a rectangle of width font_width and height
font_height (defined in cards.h). This avoids the
messy calculations involving descenders and bearings.
- void help(char *filename, char *text)
- This invokes the help system. Pass a file name and a pointer to
the file's data as prepared by text2c. If the file exists,
it is used, else the data is used. All the events are redirected to
the help system until it decides to give control back.
- void snap_to_grid(int *x, int *y,
int step_x, int step_y,
int origin_x, int origin_y,
int max_distance)
- This function is a general-purpose grid alignment function. Pass
the coordinates you want to align as x and y. The
grid is determined by the step and origin
parameters, with the origin indicating one of the points to
snap to. If the given point is more than max_distance from
one of the grid points, x and y are unchanged, else
x and y are replaced with the closest grid point.
Note that max_distance is not a pythagorian distance, it's an
absolute value of the distance along each axis independently (i.e. it
defines a square of side 2*max_distance centered on the grid
point, not a circle of radius max_distance).
Stack Functions
The stack functions all deal with stacks of cards. Each card is
described by a single number that encodes its value, suit, and whether
the card is face up or face down. cards.h provides a number
of macros for dealing with these numbers. Also, the standard ace
library provides all the images matched up with these numbers, so you
do not need to worry about them. Each stack is positioned on the
table, and may be neatly piled up or fanned to the right or down.
Note that the type Stack is an opaque pointer. All stacks
are of type Stack * but there are no fields you may access
directly.
- MAKE_CARD(s, v, f)
- This macro combines the suit (s), value (v),
and face-upness (f) into a single card number. The suit is
one of SUIT_HEARTS, SUIT_DIAMONDS,
SUIT_CLUBS, or SUIT_SPADES. Note that these are
0..3, but don't rely on that. The value is 1..13,
but you may use ACE, 2..10, JACK,
QUEEN, and KING as well. Zero is reserved for "no
card", so a zero value is not used to refer to a card. The
face-upness is either FACEUP or FACEDOWN. The macro
returns the card number.
- SUIT(c)
- Takes a card number and returns its suit (the SUIT_
macros above).
- COLOR(c)
- Returns the "color" of a card (red or black). Note that no
specific color values are defined; the only thing you can do with the
returned value of this macro is compare it to other values returned by
this macro. In other words, you can tell if two cards are the same or
different colors, but nothing else.
- VALUE(c)
- Returns the value of a card (ACE, 2..10,
JACK, QUEEN, KING).
- FACEDOWNP(c)
- The face-down predicate returns the face-downness of a card. It
returns either FACEUP or FACEDOWN.
- Stack * stack_create(int x, int y)
- This function creates a stack and positions it on the table. The
stack is initially empty and is not fanned in any direction (i.e. the
cards are neatly piled upon each other).
- void stack_destroy(Stack *s)
- This function destroys a stack and frees up the resources it was
using. Most solitaire-type games won't ever destroy stacks, they'll
just reshuffle the cards among them.
- void stack_set_pictures(Picture **fronts, Picture *back)
- This function allows the application to specify the pictures to be
used to display the cards. The fronts array is indexed by
the card number (i.e. MAKE_CARD with FACEUP); you should
allow for 64 slots in your array. The back picture is for
all face-down cards. All pictures should be CARD_WIDTH by
CARD_HEIGHT pixels.
- void stack_load_standard_deck()
- This function loads all the standard card pictures from the
built-in library. Most card games should call this once in
init().
- void stack_move(Stack *s, int x, int y)
- This function moves a stack to a new location on the screen.
While not generally useful to common solitaire games, it is used
internally to drag and drop cards between stacks.
- void stack_set_offset(Stack *s, int which_offset)
- This function changes the offset for each card in a stack. By
offsetting cards, the player can view parts of obscured cards, thus
being able to see the value of the card. Valid values for
which_offset are STACK_OFFSET_NONE,
STACK_OFFSET_RIGHT, STACK_OFFSET_DOWN,
STACK_OFFSET_TBRIGHT, and STACK_OFFSET_TBDOWN.
The TB versions are for a "tiny bit" of offset.
- void stack_set_card_size(int width, int height)
- Request that the card size be changed, such as after a resize.
Note that the values you give are requests - the library may need to
use a different size. Use stack_get_card_size to find out
what was really used.
- void stack_get_card_size(int *width, int *height)
- Find out how big the current cards are.
- void stack_get_fans(int *down, int *right, int *tbdown,
int *tbright)
- Get the offsets for fan_down, etc.
- void stack_set_empty_picture(Stack *s, Picture *p)
- This lets you specify a picture that is drawn when the stack is
empty. For example, some games want it to look like you're stacking
the cards on top of something. This is where you specify the
something. There is a standard picture called empty.gif that
simulates a small platform for the cards.
- void stack_redraw()
- This function should be called from your redraw()
function (the default redraw does this for you if you don't
specify one yourself). This causes all stacks to redraw their
cars.
- void stack_peek_card(Stack *s, int n, int show)
- This function lets the player "peek" at an otherwise obscured
card. What it does is force that card to be drawn on top of all the
other cards in the stack, regardless of where it is in the stack.
This is used to support the right-mouse button in solitaire, which
lets you see the suit of obscured cards. Note that this function will
not show you the face of a face-down card. Note that when n
(the index of the card in the stack) is zero, it means the bottom-most
card on the deck (the one closest to the table, not the one closest to
the bottom of your monitor). The top-most card (the one the player
sees the most of) has the highest number, one less than
stack_count_cards() returns. Pass a non-zero value for
show to show the card, or pass zero to put the card back and
show the stack normally.
- int stack_count_cards(Stack *s)
- Returns the number of cards in the stack. Zero means empty.
- int stack_get_card(Stack *s, int n)
- Returns the number of the given card on the stack (suitable for
the SUIT, COLOR, and VALUE macros, etc).
- void stack_add_card(Stack *s, int c)
- Adds one card to the top of the given stack.
- int stack_take_card(Stack *s)
- Removes one card from the top of the stack and returns it.
- void stack_change_card(Stack *s, int n, int c)
- Changes the given card to the given number. Note that whan
n is zero, that indicates the bottom-most card. This
function is most often used to change a card from FACEDOWN to
FACEUP.
- void stack_shuffle(Stack *s)
- This function randomizes the sequence of the cards stored in the
stack. To simulate a deck of cards, you'd create one stack and fill
it with all 52 combinations (or more, depending on the game) and
shuffle it. Then, you'd use one of the other functions (like
stack_flip_card) to move cards from this "deck" to the other
stacks. To restart the game, use stack_flip_stack to move
them all back to the deck, shuffle again, and start over.
- int stack_find(int x, int y, Stack **stack_ret, int *n_ret)
- This function is used by the click type events to
identify which card the player selected. You pass it the x,y
of the event, and a pointer to a stack pointer and integer. If the
player selected a card, the stack_ret and n_ret
variables are set to the stack and index into the stack they selected
(when n_ret is zero, they selected the bottom-most card) and
a non-zero value is returned. If the player did not select a card,
stack_ret and n_ret are unchanged and a zero value
is returned.
- int stack_drag_find(int x, int y, Stack **stack_ret)
- When dragging a stack, this function tells you which stack you
have dragged it over. Similar to stack_find. Note that the
library remembers what you're dragging from stack_begin_drag
and will do the Right Thing.
- int stack_card_posn(Stack *s, int n, int *x, int *y)
- Given a card in a stack (when n is zero, it refers to the
bottom-most card), find its position on the table. This takes into
account the offset the stack might have. The size of the card is
CARD_WIDTH by CARD_HEIGHT. If the card specified
exists, x and y are set to the coordinates of the
upper left pixel and a non-zero value is returned, else zero is
returned and x and y are unchanged.
- int stack_move_cards(Stack *src, int n, Stack *dest)
- Causes card n from stack src, and all cards on
top of it to be moved to the top of stack dest. The
face-upness is not changed. When n is zero, all cards are
moved.
- void stack_flip_card(Stack *src, Stack *dest)
- Moves the top-most card from src to dest, and
makes it face-up at the same time.
- void stack_flip_stack(Stack *src, Stack *dest)
- Moves all the cards from src to dest and makes
all the moved cards face-down. Note that the order is preserved as if
the player had flipped the whole stack; the card that used to be on
the bottom of src will be at the top of dest.
- void stack_animate(Stack *src, Stack *dest)
- Moves one card from src to dest, but does it in
such a way that the player can see it moving from one to the
other. You should use this for auto-play so that the player can see
what's happening.
- void stack_undo()
- All operations on stacks are recorded. Calling this function
reverses those operations. You may continue calling this function,
and it will continue reversing the operations, until no more exist.
Calling it when no further undo is available causes no actions.
- void stack_undo_reset()
- This function erases all undo information. You should call this
function after dealing the cards each time you start (or restart) a
game, so that the player cannot undo the deal itself.
- void stack_begin_drag(Stack *s, int n, int x, int y)
- This function is one of three that coordinate drag-and-drop
between stacks. With this function, you identify what you are picking
up - the stack, the index of the card within the stack (when
n is zero, the whole stack is picked up), and the coordinates
of the pointer at the time you picked it up. If you call this
function while a drag is in progress, the previous drag is
cancelled.
- void stack_continue_drag(int n, int x, int y)
- Called in your drag() function, this tells the library to
move the cards you've picked up to a new location to follow the
pointer. The library maintains a constant offset relative to the
pointer to give it a "natural" feel (the card don't jump into your
hand, for example). You also must pass the index of the card that is
allowed to be dropped at that time (use stack_drag_find). As
you pass different values of n to this function, the window
will change to reflect carrying the right number of cards. If you
pass n greater than the largest card index (see
stack_count_cards), a special graphic that indicates "drop
not allowed here" is used.
- void stack_drop(Stack *onto, int n)
- This function ends a drag-and-drop operation by specifying which
cards from the original stack are to be dropped onto onto.
You may pass the original stack as onto or pass n
greater than the largest card index (see stack_count_cards)
to cancel a drag operation.
Adding Pictures
It is to be expected that most applications will have some pictures
that are specific to that application. For example, the splash screen
("Welcome to my game") usually has the name of the application, so it
will be specific. Each application is allowed to create one
additional library of pictures that can be linked into your program;
the library has the other one. To add images, paint your images in
standard PNG format (no animations, transparency is supported). Then,
call make-imglib like this:
gif2pack -i $(srcdir) -n appimglib - mypictures.c *.png
Of course, replace mypictures.c and *.png with
appropriate stuff for your application. This will create a C source
file that contains your image data. Then, compile and link that
source with your other sources. By calling your picture library
appimglib, the ace library functions will automatically use
it. When you call get_picture, pass the name of the original
file (*.png) but without path info or ".png". Run
"make-imglib -h" for a detailed list of options.
Calling X Directly
If you must call X directly (to draw lines or whatnot), look in xwin.c
for a list of non-static variables you can use after your
init() function is called. These are:
extern Display *display;
extern int screen;
extern Visual *visual;
extern Colormap cmap;
extern Window window;
extern Window rootwin;
extern GC gc;
extern XFontStruct *font;
You'll also need to include whatever X headers you need. Note that
during exposures, the clip mask in the gc is set as needed. If you
must mess with the clip mask, be careful to restore it if you can, or
create your own GC to draw with.
If you do this, your program will not be portable to other
windowing systems if/when that ever happens.
Online Help
To add online help to your application, you need to write the help
file in plain HTML. There are only a few HTML constructs that are
recognized, and you must write valid HTML. Note that
closing tags are not optional if you want it to work right.
Here are the supported tags. You must use lower case, and when the
tag allows options (like img) they must be provided in the
order given with exactly the syntax shown. The HTML parser will not
accept just any HTML file. When a closing tag is shown, it is
mandatory. Tags may be nested, but do not need to be properly nested
(you should properly nest them, but the program won't blow up
if you don't).
- <b> . . . </b>
- Bold
- <i> . . . </i>
- Italics
- <tt> . . . </tt>
- Teletype (monospaced) font
- <big> . . . </big>
- Big font
- <h1> . . . </h1>
- Primary headers. These are used to create the tabs that separate
pages, so don't use any other HTML inside an H1, avoid newlines, and
keep it short.
- <h2> . . . </h2>
- Secondary headers. Same as <p><big><b>. You
should put one of these (with more elaborate text) after each H1 so
that each page has a title banner.
- <p> . . . </p>
- Paragraph. Note that both <p> and </p> do exactly
the same thing - they cause the next "thing" (word or picture) to
start a new paragraph - but you should use them properly anyway.
- <br>
- Line break. The next "thing" will be at the left margin of the
next line. Multiple BR act like one BR.
- <img src=file>
- <img align=center src=file>
- <img align=left src=file>
- <img align=right src=file>
- An image. No quotes around the file name, which is as you'd pass
to get_picture (since that's what it does). The
align=center variant aligns the picture vertically centered
on the line. If text is included in the line also, the text
centerline (not the baseline, but half the height of an "m") aligns
with the image centerline. Left and right alignment cause the image
to float to the margins, with text wrapping around them.
While developing the HTML help, run your application such that the
filename passed to help() refers to your HTML file. Then,
press F1 to invoke the online help of your application, and
it will read the file instead of using the built-in text. Each time
you exit help (F1 again, or Esc) and re-invoke it,
it re-reads the help file (you don't need to exit your
application).
When you're done, use text2c to convert the HTML file to a C
source file, which you can compile and link in to your application:
text2c my_help < mygame-help.html > mygame-help.c
That first argument (my_help in this example) defines the
name of a symbol which you then refer to in your program:
extern char my_help[];
...
help("mygame-help.html", my_help);
Copyright © 2001 DJ Delorie