// xsc: Copyright (c) 1993-2005 by Mark B. Hanson (mbh@panix.com). static const char *const file_id = "@(#)$Id: xsc.C,v 3.23 2005/01/02 19:11:17 mark Exp $"; #include #include #include #include #include "global.h" #include "random.h" #include "args.h" #include "timing.h" #include "trig.h" #include "util.h" #include "xsc.h" #include "castle.h" #include "king.h" #include "minefield.h" #include "ship.h" #include "starfield.h" #include "stats.h" #include "icon.xbm" // extern struct Args::info args; char *program; Window main_window; Window stats_window; Window game_window; Display *display; int wwidth; int wwidth2; int mwheight; int mwheight2; int gwheight; int gwheight2; Stamp time_now; namespace { Ship *ship; King *king; Castle *castle; Minefield *minefield; Stats *stats; Starfield *dark_starfield; Starfield *light_starfield; bool iconified = false; XEvent event; const float split_ratio = 0.93; const float aspect = (4.0 / 3.0) * split_ratio; int screen_number; XWMHints wm_hints; Atom delete_atom; void key_repeat(const bool on) { XKeyboardControl kbcontrol; static XKeyboardState origkbstate = (XGetKeyboardControl(display, &origkbstate), origkbstate); kbcontrol.auto_repeat_mode = (on ? origkbstate.global_auto_repeat : AutoRepeatModeOff); XChangeKeyboardControl(display, KBAutoRepeatMode, &kbcontrol); } // ::key_repeat void pause_stuff(const bool pause_state) { castle->pause(pause_state); king->pause(pause_state); minefield->pause(pause_state); ship->pause(pause_state); } // :: pause_stuff void quit(const int code) { key_repeat(true); delete ship; delete king; delete castle; delete minefield; delete stats; delete dark_starfield; delete light_starfield; XDestroySubwindows(display, main_window); free_all_gcs(); XCloseDisplay(display); exit(code); } // ::quit extern "C" RETSIGTYPE catcher(int sig) { fprintf(stderr, "xsc: caught signal %d -- shutting down\n", sig); quit(EXIT_FAILURE); } // ::catcher inline bool handle_event(void) { if (!XPending(display)) { // no events to handle return false; } XNextEvent(display, &event); switch (event.type) { case Expose: if (event.xexpose.count != 0) { // more exposure events on the way, we can ignore this one break; } if (stats->state != STATE_PAUSED) { stats->erase(); king->erase(); minefield->erase(); // the other stuff doesn't change shape, // so doesn't really need to be erased } stats->draw(); dark_starfield->draw(castle, ship); light_starfield->draw(castle, ship); ship->draw(); castle->draw(); king->draw(); minefield->draw(); // flush events to avoid flickering XSync(display, False); break; case ConfigureNotify: { int nw = event.xconfigure.width; int nh = event.xconfigure.height; int ngh = (int)(nh * split_ratio); if (nw == wwidth && nh == mwheight) { // window not resized, ignore event break; } stats->resize(nw, nh - ngh); ship->resize(nw, ngh); king->resize(nw, ngh); castle->resize(nw, ngh); minefield->resize(nw, ngh); dark_starfield->resize(nw, ngh); light_starfield->resize(nw, ngh); wwidth = nw; mwheight = nh; gwheight = ngh; wwidth2 = wwidth / 2; mwheight2 = mwheight / 2; gwheight2 = gwheight / 2; XResizeWindow(display, stats_window, wwidth, mwheight - gwheight); XMoveResizeWindow(display, game_window, 0, mwheight - gwheight, wwidth, gwheight); } break; case KeyPress: { KeySym ks = XLookupKeysym(&event.xkey, 0); if (ks == args.ccw) { if (stats->state == STATE_NORMAL) { ship->rotate_ccw(KEY_DOWN); } else { XBell(display, 50); } } else if (ks == args.cw) { if (stats->state == STATE_NORMAL) { ship->rotate_cw(KEY_DOWN); } else { XBell(display, 50); } } else if (ks == args.thrust) { if (stats->state == STATE_NORMAL) { ship->thrust(KEY_DOWN); } else { XBell(display, 50); } } else if (ks == args.fire) { if (stats->state == STATE_NORMAL) { ship->fire(); } else { XBell(display, 50); } } else if (ks == args.pause) { if (stats->state == STATE_OVER || iconified) { XBell(display, 50); } else { if (stats->state == STATE_PAUSED) { stats->state = STATE_NORMAL; pause_stuff(false); XWarpPointer(display, main_window, stats_window, 0, 0, wwidth, mwheight, wwidth2, (mwheight - gwheight) / 2); key_repeat(false); } else { stats->state = STATE_PAUSED; pause_stuff(true); key_repeat(true); } } } else if (ks == args.iconify) { if (iconified) { XWithdrawWindow(display, main_window, screen_number); XSetWMHints(display, main_window, &wm_hints); XMapWindow(display, main_window); iconified = false; } else { XIconifyWindow(display, main_window, screen_number); iconified = true; if (stats->state == STATE_NORMAL) { stats->state = STATE_PAUSED; pause_stuff(true); } key_repeat(true); } } else if (ks == args.start) { if (stats->state == STATE_OVER) { stats->reset(); castle->refresh(); minefield->erase(); minefield->snuff(); ship->reincarnate(); } else { XBell(display, 50); } } else if (ks == args.quit) { quit(EXIT_SUCCESS); } else { XBell(display, 50); } } break; case KeyRelease: { KeySym ks = XLookupKeysym(&event.xkey, 0); if (ks == args.ccw) { if (stats->state == STATE_NORMAL) { ship->rotate_ccw(KEY_UP); } } else if (ks == args.cw) { if (stats->state == STATE_NORMAL) { ship->rotate_cw(KEY_UP); } } else if (ks == args.thrust) { if (stats->state == STATE_NORMAL) { ship->thrust(KEY_UP); } } } break; case LeaveNotify: if (stats->state == STATE_NORMAL) { stats->state = STATE_PAUSED; pause_stuff(true); } key_repeat(true); break; case EnterNotify: if (stats->state == STATE_OVER) { key_repeat(false); } break; case MapNotify: iconified = false; break; case UnmapNotify: iconified = true; if (stats->state == STATE_NORMAL) { stats->state = STATE_PAUSED; pause_stuff(true); } key_repeat(true); break; case ClientMessage: if (event.xclient.format == 32 && (Atom)event.xclient.data.l[0] == delete_atom) { quit(EXIT_SUCCESS); } break; default: // ignore it. break; } return true; } // ::handle_event inline void animate(void) { if (stats->changed()) { stats->erase(); stats->draw(); } if (stats->state == STATE_PAUSED) { return; } // erase the last frame dark_starfield->erase(castle, ship); light_starfield->erase(castle, ship); king->erase(); castle->erase(); ship->erase(); minefield->erase(); // calculate new positions dark_starfield->turn(); dark_starfield->move(); light_starfield->turn(); light_starfield->move(); ship->turn(); ship->move(castle, king, minefield, stats); king->turn(castle, ship); castle->turn(minefield, king); minefield->move(castle, ship); minefield->launch(king, castle); // maybe launch another buzzer // draw new frame dark_starfield->draw(castle, ship); light_starfield->draw(castle, ship); king->draw(); castle->draw(); ship->draw(); minefield->draw(); // flush events XSync(display, False); } // ::animate void snooze(void) { static const long desired = (long)((1.0 / args.fps) * 1000000); static Stamp tv_begin = (XSCTime::now(&tv_begin), tv_begin); static long sleepfor; XSCTime::now(&time_now); sleepfor += desired - (time_now - tv_begin).micros(); if (sleepfor > 0) { // sleep if we have the time to spare XSCTime::sleep(sleepfor); } else if (sleepfor < desired * -2L) { // keep the game from running wild if we have a sleep deficit sleepfor = desired * -2L; } tv_begin = time_now; } // ::snooze } // namespace int main(const int argc, char **const argv) { char *display_name = NULL; char title[] = "xsc"; char *window_title = title; char *icon_title = title; Pixmap icon; XSizeHints size_hints; XClassHint class_hints; XTextProperty windowName, iconName; Screen *screen; int display_width, display_height; program = argv[0]; printf("%s version %s\n", title, VERSION); XSCTime::now(&time_now); Random::seed((unsigned int)time_now.get_usec()); Trig::init(); Args::init(&args); Args::set(&args, argc, argv); if ((display = XOpenDisplay(display_name)) == NULL) { fprintf(stderr, "%s: cannot connect to X server %s\n", program, XDisplayName(display_name)); exit(1); } screen_number = DefaultScreen(display); screen = DefaultScreenOfDisplay(display); display_width = DisplayWidth(display, screen_number); display_height = DisplayHeight(display, screen_number); mwheight = (int)(display_height * args.percent / 100.0); gwheight = (int)(mwheight * split_ratio); wwidth = (int)(mwheight * aspect); mwheight2 = mwheight / 2; gwheight2 = gwheight / 2; wwidth2 = wwidth / 2; main_window = XCreateSimpleWindow(display, RootWindow(display, screen_number), 0, 0, wwidth, mwheight, 1, WhitePixel(display, screen_number), BlackPixel(display, screen_number)); stats_window = XCreateSimpleWindow(display, main_window, 0, 0, wwidth, mwheight - gwheight, 0, WhitePixel(display, screen_number), BlackPixel(display, screen_number)); game_window = XCreateSimpleWindow(display, main_window, 0, mwheight - gwheight, wwidth, gwheight, 0, WhitePixel(display, screen_number), BlackPixel(display, screen_number)); icon = XCreateBitmapFromData(display, main_window, (char *)icon_bits, icon_width, icon_height); if (XStringListToTextProperty(&window_title, 1, &windowName) == 0) { fprintf(stderr, "%s: structure allocation for windowName failed.\n", program); exit(1); } if (XStringListToTextProperty(&icon_title, 1, &iconName) == 0) { fprintf(stderr, "%s: structure allocation for iconName failed.\n", program); exit(1); } size_hints.flags = PSize | PAspect | PMinSize | PMaxSize; size_hints.min_aspect.x = size_hints.max_aspect.x = size_hints.min_width = (int)(100.0 * aspect); size_hints.min_aspect.y = size_hints.max_aspect.y = size_hints.min_height = 100; size_hints.max_width = display_width; size_hints.max_height = display_height; wm_hints.flags = StateHint | IconPixmapHint | InputHint; wm_hints.initial_state = NormalState; wm_hints.input = True; wm_hints.icon_pixmap = icon; class_hints.res_name = program; class_hints.res_class = window_title; XSetWMProperties(display, main_window, &windowName, &iconName, argv, argc, &size_hints, &wm_hints, &class_hints); delete_atom = XInternAtom(display, "WM_DELETE_WINDOW", False); XSetWMProtocols(display, main_window, &delete_atom, 1); XSelectInput(display, main_window, (ExposureMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask | LeaveWindowMask | EnterWindowMask)); XSelectInput(display, stats_window, ExposureMask); XSelectInput(display, game_window, ExposureMask); XMapWindow(display, main_window); XMapWindow(display, stats_window); XMapWindow(display, game_window); XNextEvent(display, &event); XSync(display, True); init_gc(); ship = new Ship; king = new King; castle = new Castle; stats = new Stats; minefield = new Minefield; dark_starfield = new Starfield(100, false); light_starfield = new Starfield(50, true); key_repeat(false); signal(SIGHUP, catcher); signal(SIGINT, catcher); signal(SIGQUIT, catcher); signal(SIGSEGV, catcher); signal(SIGTERM, catcher); for (;;) { while (handle_event()); animate(); snooze(); } quit(EXIT_FAILURE); } // ::main