// // "Block Attack!" scrolling blocks game using the Fast Light Tool Kit (FLTK). // // Copyright © 2006-2021 by Michael Sweet. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this // file is missing or damaged, see the license at: // // https://www.fltk.org/COPYING.php // // Please see the following page on how to report bugs and issues: // // https://www.fltk.org/bugs.php // #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Audio headers... #include #ifndef _WIN32 # include # include // gettimeofday() #endif // !_WIN32 #ifdef HAVE_ALSA_ASOUNDLIB_H # define ALSA_PCM_NEW_HW_PARAMS_API # include #endif // HAVE_ALSA_ASOUNDLIB_H #ifdef __APPLE__ # include #endif // __APPLE__ #ifdef _WIN32 # include #endif // _WIN32 #define BLOCK_COLS 25 #define BLOCK_ROWS 15 #define BLOCK_SIZE 32 #define BLOCK_BLAST 100 // These factors are used to fine-tune the game when these events // occur by multiplying the timer interval with the given factor: // // (a) enter the next game level // (b) click on a "normal block", destroy this one and adjacent blocks // (c) click on a "bomb", destroy all blocks of the same color #define LEVEL_FACTOR 0.900f // was: 0.95 #define NORMAL_FACTOR 0.999f #define BOMB_FACTOR 0.995f // Set this to 1 to debug the timer callback (should be 0) #define DEBUG_TIMER 0 #include "pixmaps/blast.xpm" Fl_Pixmap blast_pixmap(blast_xpm); #include "pixmaps/red.xpm" Fl_Pixmap red_pixmap(red_xpm); #include "pixmaps/red_bomb.xpm" Fl_Pixmap red_bomb_pixmap(red_bomb_xpm); #include "pixmaps/green.xpm" Fl_Pixmap green_pixmap(green_xpm); #include "pixmaps/green_bomb.xpm" Fl_Pixmap green_bomb_pixmap(green_bomb_xpm); #include "pixmaps/blue.xpm" Fl_Pixmap blue_pixmap(blue_xpm); #include "pixmaps/blue_bomb.xpm" Fl_Pixmap blue_bomb_pixmap(blue_bomb_xpm); #include "pixmaps/yellow.xpm" Fl_Pixmap yellow_pixmap(yellow_xpm); #include "pixmaps/yellow_bomb.xpm" Fl_Pixmap yellow_bomb_pixmap(yellow_bomb_xpm); #include "pixmaps/cyan.xpm" Fl_Pixmap cyan_pixmap(cyan_xpm); #include "pixmaps/cyan_bomb.xpm" Fl_Pixmap cyan_bomb_pixmap(cyan_bomb_xpm); #include "pixmaps/magenta.xpm" Fl_Pixmap magenta_pixmap(magenta_xpm); #include "pixmaps/magenta_bomb.xpm" Fl_Pixmap magenta_bomb_pixmap(magenta_bomb_xpm); #include "pixmaps/gray.xpm" Fl_Pixmap gray_pixmap(gray_xpm); #include "pixmaps/gray_bomb.xpm" Fl_Pixmap gray_bomb_pixmap(gray_bomb_xpm); Fl_Pixmap *normal_pixmaps[] = { &red_pixmap, &green_pixmap, &blue_pixmap, &yellow_pixmap, &cyan_pixmap, &magenta_pixmap, &gray_pixmap }; Fl_Pixmap *bomb_pixmaps[] = { &red_bomb_pixmap, &green_bomb_pixmap, &blue_bomb_pixmap, &yellow_bomb_pixmap, &cyan_bomb_pixmap, &magenta_bomb_pixmap, &gray_bomb_pixmap }; const unsigned char screen_bits[] = { 0xff, 0x55, 0xff, 0xaa, 0xff, 0x55, 0xff, 0xaa }; Fl_Bitmap screen_bitmap(screen_bits, 8, 8); Fl_Tiled_Image screen_tile(&screen_bitmap); // Sound class... // // There are MANY ways to implement sound in a FLTK application. // The approach we are using here is to conditionally compile OS- // specific code into the application - CoreAudio for MacOS X, the // standard Win32 API stuff for Windows, ALSA or X11 for Linux, and // X11 for all others. We have to support ALSA on Linux because the // current Xorg releases no longer support XBell() or the PC speaker. // // There are several good cross-platform audio libraries we could also // use, such as OpenAL, PortAudio, and SDL, however they were not chosen // for this application because of our limited use of sound. // // Many thanks to Ian MacArthur who provided sample code that led to // the CoreAudio implementation you see here! class BlockSound { // Private, OS-specific data... #ifdef __APPLE__ AudioDeviceID device; #ifndef MAC_OS_X_VERSION_10_5 #define MAC_OS_X_VERSION_10_5 1050 #endif # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 AudioDeviceIOProcID audio_proc_id; # endif AudioStreamBasicDescription format; short *data; int remaining; static OSStatus audio_cb(AudioDeviceID device, const AudioTimeStamp *current_time, const AudioBufferList *data_in, const AudioTimeStamp *time_in, AudioBufferList *data_out, const AudioTimeStamp *time_out, void *client_data); #elif defined(_WIN32) HWAVEOUT device; HGLOBAL header_handle; LPWAVEHDR header_ptr; HGLOBAL data_handle; LPSTR data_ptr; #else # ifdef HAVE_ALSA_ASOUNDLIB_H snd_pcm_t *handle; # endif // HAVE_ALSA_ASOUNDLIB_H #endif // __APPLE__ public: // Common data... static short *sample_data; static int sample_size; BlockSound(); ~BlockSound(); void play_explosion(float duration); }; // Sound class globals... short *BlockSound::sample_data = NULL; int BlockSound::sample_size = 0; // Initialize the BlockSound class BlockSound::BlockSound() { sample_size = 0; #ifdef __APPLE__ remaining = 0; UInt32 size = sizeof(device); if (AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, (void *)&device) != noErr) return; size = sizeof(format); if (AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyStreamFormat, &size, &format) != noErr) return; // Set up a format we like... format.mSampleRate = 44100.0; // 44.1kHz format.mChannelsPerFrame = 2; // stereo if (AudioDeviceSetProperty(device, NULL, 0, false, kAudioDevicePropertyStreamFormat, sizeof(format), &format) != noErr) return; // Check we got linear pcm - what to do if we did not ??? if (format.mFormatID != kAudioFormatLinearPCM) return; // Attach the callback and start the device # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 if (AudioDeviceCreateIOProcID(device, audio_cb, (void *)this, &audio_proc_id) != noErr) return; AudioDeviceStart(device, audio_proc_id); # else if (AudioDeviceAddIOProc(device, audio_cb, (void *)this) != noErr) return; AudioDeviceStart(device, audio_cb); # endif sample_size = (int)format.mSampleRate; #elif defined(_WIN32) WAVEFORMATEX format; memset(&format, 0, sizeof(format)); format.cbSize = sizeof(format); format.wFormatTag = WAVE_FORMAT_PCM; format.nChannels = 2; format.nSamplesPerSec = 44100; format.nAvgBytesPerSec = 44100 * 4; format.nBlockAlign = 4; format.wBitsPerSample = 16; data_handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, format.nSamplesPerSec * 4); if (!data_handle) return; data_ptr = (LPSTR)GlobalLock(data_handle); header_handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR)); if (!header_handle) return; header_ptr = (WAVEHDR *)GlobalLock(header_handle); header_ptr->lpData = data_ptr; header_ptr->dwFlags = 0; header_ptr->dwLoops = 0; if (waveOutOpen(&device, WAVE_MAPPER, &format, 0, 0, WAVE_ALLOWSYNC) != MMSYSERR_NOERROR) return; sample_size = format.nSamplesPerSec; #else # ifdef HAVE_ALSA_ASOUNDLIB_H handle = NULL; if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) >= 0) { // Initialize PCM sound stuff... snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca(¶ms); snd_pcm_hw_params_any(handle, params); snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16); snd_pcm_hw_params_set_channels(handle, params, 2); unsigned rate = 44100; int dir; snd_pcm_hw_params_set_rate_near(handle, params, &rate, &dir); snd_pcm_uframes_t period = (int)rate; snd_pcm_hw_params_set_period_size_near(handle, params, &period, &dir); sample_size = rate; if (snd_pcm_hw_params(handle, params) < 0) { sample_size = 0; snd_pcm_close(handle); handle = NULL; } } # endif // HAVE_ALSA_ASOUNDLIB_H #endif // __APPLE__ if (sample_size) { // Make an explosion sound by passing white noise through a low pass // filter with a decreasing frequency... sample_data = new short[2 * sample_size]; short *sample_ptr = sample_data; int max_sample = 2 * sample_size - 2; *sample_ptr++ = 0; *sample_ptr++ = 0; for (int j = max_sample; j > 0; j --, sample_ptr ++) { float freq = (float)j / (float)max_sample; float volume = float(32767.0 * (0.5 * sqrt(freq) + 0.5)); float sample = float(0.0001 * ((rand() % 20001) - 10000)); *sample_ptr = (int)(volume * freq * sample + (1.0 - freq) * sample_ptr[-2]); } } } // Cleanup the BlockSound class BlockSound::~BlockSound() { #ifdef __APPLE__ if (sample_size) { # if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 AudioDeviceStop(device, audio_proc_id); AudioDeviceDestroyIOProcID(device, audio_proc_id); # else AudioDeviceStop(device, audio_cb); AudioDeviceRemoveIOProc(device, audio_cb); # endif } #elif defined(_WIN32) if (sample_size) { waveOutClose(device); GlobalUnlock(header_handle); GlobalFree(header_handle); GlobalUnlock(data_handle); GlobalFree(data_handle); } #else # ifdef HAVE_ALSA_ASOUNDLIB_H if (handle) { snd_pcm_drain(handle); snd_pcm_close(handle); } # endif // HAVE_ALSA_ASOUNDLIB_H #endif // __APPLE__ if (sample_size) { delete[] sample_data; } } #ifdef __APPLE__ // Callback function for writing audio data... OSStatus BlockSound::audio_cb(AudioDeviceID device, const AudioTimeStamp *current_time, const AudioBufferList *data_in, const AudioTimeStamp *time_in, AudioBufferList *data_out, const AudioTimeStamp *time_out, void *client_data) { BlockSound *ss = (BlockSound *)client_data; int count; float *buffer; if (!ss->remaining) return noErr; for (count = data_out->mBuffers[0].mDataByteSize / sizeof(float), buffer = (float*) data_out->mBuffers[0].mData; ss->remaining > 0 && count > 0; count --, ss->data ++, ss->remaining --) { *buffer++ = *(ss->data) / 32767.0; } while (count > 0) { *buffer++ = 0.0; count --; } return noErr; } #endif // __APPLE__ // Play a note for the given amount of time... void BlockSound::play_explosion(float duration) { Fl::check(); if (duration <= 0.0) return; #if defined(__APPLE__) || defined(_WIN32) || defined(HAVE_ALSA_ASOUNDLIB_H) if (duration > 1.0) duration = 1.0; int samples = (int)(duration * sample_size); short *sample_ptr = sample_data + 2 * (sample_size - samples); #endif // __APPLE__ || _WIN32 || HAVE_ALSA_ASOUNDLIB_H #ifdef __APPLE__ // Point to the next note... data = sample_ptr; remaining = samples * 2; #elif defined(_WIN32) if (sample_size) { memcpy(data_ptr, sample_ptr, samples * 4); header_ptr->dwBufferLength = samples * 4; waveOutPrepareHeader(device, header_ptr, sizeof(WAVEHDR)); waveOutWrite(device, header_ptr, sizeof(WAVEHDR)); } else Beep(440, (int)(1000.0 * duration)); #elif defined(HAVE_ALSA_ASOUNDLIB_H) if (handle) { // Use ALSA to play the sound... if (snd_pcm_writei(handle, sample_ptr, samples) < 0) { snd_pcm_prepare(handle); snd_pcm_writei(handle, sample_ptr, samples); } return; } #endif // __APPLE__ } class BlockWindow : public Fl_Double_Window { public: struct Block { int color; bool bomb; float y; }; struct Column { int num_blocks; Block blocks[BLOCK_ROWS]; float x; }; private: int frames_, frames_per_second_; time_t frame_time_; bool show_fps_; Fl_Button *help_button_, *play_button_; int num_columns_; Column columns_[BLOCK_COLS]; int count_; bool help_; int high_score_; float interval_; int level_; int num_colors_; int opened_columns_; bool paused_; static Fl_Preferences prefs_; int score_; BlockSound *sound_; char title_[255]; int title_y_; void _BlockWindow(); int bomb(int color); int click(int col, int row); static void help_cb(Fl_Widget *wi, BlockWindow *bw); void init(); static void play_cb(Fl_Widget *wi, BlockWindow *bw); static void timeout_cb(BlockWindow *bw); public: BlockWindow(int X, int Y, int W, int H, const char *L = 0); BlockWindow(int W, int H, const char *L = 0); ~BlockWindow(); void draw() FL_OVERRIDE; int handle(int event) FL_OVERRIDE; void new_game(); int score() { return (score_); } void up_level(); }; Fl_Preferences BlockWindow::prefs_(Fl_Preferences::USER_L, "fltk.org", "blocks"); int main(int argc, char *argv[]) { Fl::scheme("plastic"); Fl::visible_focus(0); BlockWindow *bw = new BlockWindow(BLOCK_COLS * BLOCK_SIZE, BLOCK_ROWS * BLOCK_SIZE + 20, "Block Attack!"); bw->show(argc, argv); return (Fl::run()); } // Create a block window at the specified position BlockWindow::BlockWindow(int X, int Y, int W, int H, const char *L) : Fl_Double_Window(X, Y, W, H, L) { _BlockWindow(); } // Create a block window BlockWindow::BlockWindow(int W, int H, const char *L) : Fl_Double_Window(W, H, L) { _BlockWindow(); } // Delete a block window BlockWindow::~BlockWindow() { Fl::remove_timeout((Fl_Timeout_Handler)timeout_cb, (void *)this); } // Initialize a block window... void BlockWindow::_BlockWindow() { init(); help_button_ = new Fl_Button(0, 0, 20, 20, "?"); help_button_->callback((Fl_Callback *)help_cb, this); help_button_->shortcut('?'); play_button_ = new Fl_Button(80, (h() - 80) / 2, 80, 80, "@>"); play_button_->callback((Fl_Callback *)play_cb, this); play_button_->labelsize(44); play_button_->shortcut(' '); sound_ = new BlockSound(); prefs_.get("high_score", high_score_, 0); Fl::add_timeout(0.01666666, (Fl_Timeout_Handler)timeout_cb, (void *)this); } // Bomb all blocks of a given color and return the number of affected blocks int BlockWindow::bomb(int color) { int j, k; int count; Block *b; Column *c; if (color >= BLOCK_BLAST) return (0); for (j = num_columns_, c = columns_, count = 1; j > 0; j --, c ++) for (k = c->num_blocks, b = c->blocks; k > 0; k --, b ++) if (b->color == color) { b->color = -color; count ++; } return (count); } // Tag all blocks connected to the clicked block and return the number // of affected blocks int BlockWindow::click(int col, int row) { Block *b; Column *c; int count, color; c = columns_ + col; b = c->blocks + row; color = b->color; if (color < 0 || color >= BLOCK_BLAST) return (0); // Find the bottom block... while (row > 0 && b[-1].color == color) { row --; b --; } count = 0; while (row < c->num_blocks && b->color == color) { b->color = -color; if (col > 0 && row < c[-1].num_blocks && c[-1].blocks[row].color == color) { count += click(col - 1, row); } if (col < (num_columns_ - 1) && row < c[1].num_blocks && c[1].blocks[row].color == color) { count += click(col + 1, row); } count ++; row ++; b ++; } return (count); } // Draw the block window... void BlockWindow::draw() { int j, k, xx, yy; Block *b; Column *c; // Draw the blocks... fl_color(FL_BLACK); fl_rectf(0, 0, w(), h()); // Draw the blocks... for (j = num_columns_, c = columns_; j > 0; j --, c ++) for (k = c->num_blocks, b = c->blocks; k > 0; k --, b ++) { xx = w() - (int)c->x; yy = h() - BLOCK_SIZE - (int)b->y; if (b->color >= BLOCK_BLAST) { b->color ++; blast_pixmap.draw(xx, yy); } else if (b->color < 0) { if (b->bomb) bomb_pixmaps[-b->color - 1]->draw(xx, yy); else normal_pixmaps[-b->color - 1]->draw(xx, yy); } else { if (b->bomb) bomb_pixmaps[b->color - 1]->draw(xx, yy); else normal_pixmaps[b->color - 1]->draw(xx, yy); } } if (interval_ < 0.0 || paused_) { fl_color(FL_BLACK); screen_tile.draw(0, 0, w(), h(), 0, 0); } // Redraw the widgets... play_button_->redraw(); help_button_->redraw(); draw_children(); // Draw any paused/game over/new game message... if ((paused_ || interval_ < 0.0) && play_button_->w() == 80) { const char *s; if (help_) { s = "Click on adjacent blocks of the same color. Clear all blocks " "before they reach the left side."; fl_font(FL_HELVETICA_BOLD, 24); fl_color(FL_BLACK); fl_draw(s, 171, 3, w() - 250, h() - 6, (Fl_Align)(FL_ALIGN_WRAP | FL_ALIGN_LEFT)); fl_color(FL_YELLOW); fl_draw(s, 168, 0, w() - 250, h(), (Fl_Align)(FL_ALIGN_WRAP | FL_ALIGN_LEFT)); } else { if (interval_ < 0.0) { #ifdef DEBUG // Show sample waveform... short *sample_ptr; for (i = 0; i < 2; i++) { fl_color(FL_RED + i); fl_begin_line(); for (j = 0, sample_ptr = sound_->sample_data + i; j < sound_->sample_size; j ++, sample_ptr += 2) fl_vertex(j * w() / sound_->sample_size, *sample_ptr * h() / 4 / 65534 + h() / 2); fl_end_line(); } #endif // DEBUG if (num_columns_ && (time(NULL) & 7) < 4) s = "Game Over"; else s = "Block Attack!\nby Michael R Sweet"; } else s = "Paused"; fl_font(FL_HELVETICA_BOLD, 32); fl_color(FL_BLACK); fl_draw(s, 6, 6, w() - 6, h() - 6, FL_ALIGN_CENTER); fl_color(FL_YELLOW); fl_draw(s, 0, 0, w(), h(), FL_ALIGN_CENTER); } } time_t curtime = time(NULL); frames_ ++; if (curtime > frame_time_) { frames_per_second_ = (frames_per_second_ + 3 * frames_ / int(curtime - frame_time_)) / 4; frames_ = 0; frame_time_ = curtime; } // Draw the scores and level... char s[255]; snprintf(s, sizeof(s), " Score: %d", score_); fl_color(FL_WHITE); fl_font(FL_HELVETICA, 14); fl_draw(s, 40, 0, w() - 40, 20, FL_ALIGN_LEFT); snprintf(s, sizeof(s), "High Score: %d ", high_score_); fl_draw(s, 0, 0, w(), 20, FL_ALIGN_RIGHT); if (level_ > 1 || title_y_ <= 0) { snprintf(s, sizeof(s), "Level: %d ", level_); fl_draw(s, 0, 0, w(), 20, FL_ALIGN_CENTER); } if (show_fps_) { snprintf(s, sizeof(s), "FPS: %d ", frames_per_second_); fl_draw(s, 0, h() - 20, w(), 20, FL_ALIGN_LEFT); } if (title_y_ > 0 && interval_ > 0.0) { int sz = 14 + title_y_ * 86 / h(); fl_font(FL_HELVETICA_BOLD, sz); fl_color(FL_YELLOW); fl_draw(title_, 0, title_y_, w(), sz, FL_ALIGN_CENTER); } } // Handle mouse clicks, etc. int BlockWindow::handle(int event) { int j, k, mx, my, count; Block *b; Column *c; if (Fl_Double_Window::handle(event)) return (1); else if (interval_ < 0.0 || paused_) return (0); switch (event) { case FL_KEYBOARD: // '+': raise level if (Fl::event_text() && !Fl::event_state(FL_CTRL | FL_ALT | FL_META) && !strcmp(Fl::event_text(), "+")) { up_level(); return (1); } // ALT + SHIFT + 'H': clear highscore if (Fl::event_text() && (Fl::event_state() & (FL_ALT | FL_SHIFT)) == (FL_ALT | FL_SHIFT) && !strcmp(Fl::event_text(), "H")) { high_score_ = score_; prefs_.set("high_score", high_score_); return (1); } // 'f': toggle showing frames-per-second if (Fl::event_text() && !strcmp(Fl::event_text(), "f")) { show_fps_ = !show_fps_; } break; case FL_PUSH: mx = w() - Fl::event_x() + BLOCK_SIZE; my = h() - Fl::event_y(); count = 0; b = 0; for (j = 0, c = columns_; !count && j < num_columns_; j ++, c ++) for (k = 0, b = c->blocks; k < c->num_blocks; k ++, b ++) if (mx >= c->x && mx < (c->x + BLOCK_SIZE) && my >= b->y && my < (b->y + BLOCK_SIZE)) { if (b->bomb) count = bomb(b->color); else count = click(j, k); break; } if (count < 2) { for (j = 0, c = columns_; j < num_columns_; j ++, c ++) for (k = 0, b = c->blocks; k < c->num_blocks; k ++, b ++) if (b->color < 0) b->color = -b->color; } else { count --; if (b->bomb) { sound_->play_explosion(float(0.19 + 0.005 * count)); interval_ *= BOMB_FACTOR; score_ += count; } else { sound_->play_explosion(float(0.09 + 0.005 * count)); interval_ *= NORMAL_FACTOR; score_ += count * count; } if (score_ > high_score_) { high_score_ = score_; prefs_.set("high_score", high_score_); } for (j = 0, c = columns_; j < num_columns_; j ++, c ++) for (k = 0, b = c->blocks; k < c->num_blocks; k ++, b ++) if (b->color < 0) b->color = BLOCK_BLAST; } return (1); default: break; } return (0); } // Toggle the on-line help... void BlockWindow::help_cb(Fl_Widget *, BlockWindow *bw) { bw->paused_ = bw->help_ = !bw->help_; bw->play_button_->label("@>"); bw->redraw(); } // Initialize the block window... void BlockWindow::init() { frames_ = 0; frames_per_second_ = 0; frame_time_ = time(NULL); show_fps_ = false; count_ = 0; help_ = false; interval_ = -1.0; level_ = 1; num_colors_ = 3; num_columns_ = 0; paused_ = false; score_ = 0; title_[0] = '\0'; title_y_ = 0; } // Start a new game... void BlockWindow::new_game() { // Seed the random number generator... srand((unsigned int)time(NULL)); init(); interval_ = 0.01666666666f; opened_columns_ = 0; strcpy(title_, "Level: 1"); title_y_ = h(); redraw(); } // Play/pause... void BlockWindow::play_cb(Fl_Widget *wi, BlockWindow *bw) { if (bw->interval_ < 0) bw->new_game(); else bw->paused_ = !bw->paused_; if (bw->paused_) wi->label("@>"); else { wi->label("@-2||"); bw->help_ = false; } } void BlockWindow::up_level() { interval_ *= LEVEL_FACTOR; opened_columns_ = 0; if (num_colors_ < 7) num_colors_ ++; level_ ++; snprintf(title_, sizeof(title_), "Level: %d", level_); title_y_ = h(); } // Animate the game... void BlockWindow::timeout_cb(BlockWindow *bw) { int i, j; Block *b; Column *c; float lastx, lasty; #if DEBUG_TIMER static double lasttime; static double delta_sum; static double interval; double curtime; static int ntime = 0; static int level = 0; #if !defined(_WIN32) { struct timeval atime; gettimeofday(&atime, NULL); curtime = atime.tv_sec % 60 + 0.000001 * atime.tv_usec; } #else // (_WIN32) { SYSTEMTIME atime; GetLocalTime(&atime); curtime = atime.wSecond + 0.001 * atime.wMilliseconds; } #endif // (_WIN32) // platform independent part of timer debugging code if (bw->interval_ > 0) { // game is active if (bw->level_ != level) { if (ntime > 0) { printf("*** average delta time = %9.6f, n =%4d, level %d, interval %f\n", delta_sum / ntime, ntime, level, interval); fflush(stdout); } delta_sum = 0; // reset average ntime = 0; interval = bw->interval_; } double delta = curtime - lasttime; if (delta < 0) delta += 60; printf("%9.6f (%+f - %f = %9.6f), level: %d\n", curtime, delta, interval, delta - interval, level); fflush(stdout); interval = bw->interval_; level = bw->level_; delta = delta - interval; delta_sum += delta > 0 ? delta : -delta; // abs(delta) ntime++; } else { // waiting ... // printf("[OFF] %6.2f\n", curtime); // fflush(stdout); } lasttime = curtime; #endif // DEBUG_TIMER // Update blocks that have been destroyed... for (i = 0, c = bw->columns_; i < bw->num_columns_; i ++, c ++) for (j = 0, b = c->blocks; j < c->num_blocks; j ++, b ++) if (b->color > (BLOCK_BLAST + 5)) { bw->redraw(); c->num_blocks --; if (j < c->num_blocks) { memmove(b, b + 1, (c->num_blocks - j) * sizeof(Block)); } j --; b --; if (!c->num_blocks) { bw->num_columns_ --; if (i < bw->num_columns_) { memmove(c, c + 1, (bw->num_columns_ - i) * sizeof(Column)); } i --; c --; j = c->num_blocks; } } // Let the rest of the blocks fall and/or move... for (i = bw->num_columns_, c = bw->columns_, lastx = c->x; i > 0; i--, c++) { if (c->x > lastx) { c->x -= 8; bw->redraw(); } lastx = c->x + BLOCK_SIZE; if (!bw->paused_ && bw->interval_ > 0.0) { bw->redraw(); c->x += 0.25; } for (j = c->num_blocks, b = c->blocks, lasty = 0; j > 0; j --, b ++) { if (b->y > lasty) { bw->redraw(); b->y -= 4; } lasty = b->y + BLOCK_SIZE; } } // Slide the title text as needed... if (bw->title_y_ > 0) { bw->redraw(); bw->title_y_ -= 2; } // Play the game... if (!bw->paused_ && bw->interval_ > 0.0) { bw->count_ --; if (bw->count_ <= 0) { bw->redraw(); bw->count_ = 4 * BLOCK_SIZE; if (bw->num_columns_ == BLOCK_COLS) { bw->interval_ = -1.0; bw->sound_->play_explosion(0.8f); bw->play_button_->label("@>"); } else { bw->opened_columns_ ++; if (bw->opened_columns_ > (2 * BLOCK_COLS)) { bw->up_level(); } c = bw->columns_; if (bw->num_columns_) { memmove(c + 1, c, bw->num_columns_ * sizeof(Column)); } bw->num_columns_ ++; c->x = 0; c->num_blocks = BLOCK_ROWS; for (j = 0, b = c->blocks; j < BLOCK_ROWS; j ++, b ++) { b->bomb = bw->num_colors_ > 3 && (rand() & 127) < bw->num_colors_; b->color = 1 + (rand() % bw->num_colors_); b->y = float(j * (BLOCK_SIZE + 8) + 24); } } } } else { bw->count_ --; if (bw->count_ <= 0) { bw->count_ = 40; bw->redraw(); } } // Update the play/pause button as needed... if ((bw->paused_ || bw->interval_< 0.0) && bw->play_button_->w() < 80) { int s = bw->play_button_->w() + 5; bw->play_button_->resize(s, (s - 20) * (bw->h() - s) / 120, s, s); bw->play_button_->labelsize(s / 2 + 4); bw->redraw(); } else if ((!bw->paused_ && bw->interval_ > 0.0) && bw->play_button_->w() > 20) { int s = bw->play_button_->w() - 2; bw->play_button_->resize(s, (s - 20) * (bw->h() - s) / 120, s, s); bw->play_button_->labelsize(s / 2 + 4); bw->redraw(); } if (bw->interval_ > 0.0) { Fl::repeat_timeout(bw->interval_, (Fl_Timeout_Handler)timeout_cb, (void *)bw); } else { Fl::repeat_timeout(0.1, (Fl_Timeout_Handler)timeout_cb, (void *)bw); } }