/* game.cpp - Don Yang (uguu.org) DFA for this game (start at GAME_STATE_WAIT_START): (GameState > 0) indicate that game is being played. GAME_STATE_WAIT_START: [Draw title screen] if( key pressed ) reset states goto GAME_STATE_GENERATE_BLOCK decrease delay_time if( delay_time is zero ) set delay_time goto GAME_STATE_SHOW_HISTORY goto GAME_STATE_WAIT_START GAME_STATE_SHOW_HISTORY: [Draw highscore lists] if( key pressed ) set delay_time goto GAME_STATE_WAIT_START decrease delay_time if( delay_time is zero ) set delay_time goto GAME_STATE_WAIT_START goto GAME_STATE_SHOW_HISTORY GAME_STATE_GENERATE_BLOCK: [nothing drawn, DFA will always shift to next state] generate random block if( grid overflowed ) goto GAME_STATE_GAME_OVER increase block_count set block_time to max goto GAME_STATE_INPUT_LOOP GAME_STATE_INPUT_LOOP: [Draw grid and moving block, color clusters] process input if( input == drop_block ) drop block add block to grid if( block contains special tile ) goto GAME_STATE_CLEAR_SPECIAL goto GAME_STATE_MARK_CLUSTER decrease block_time if( block_time is zero ) if( can not drop block ) add block to grid if( block contains special tile ) goto GAME_STATE_CLEAR_SPECIAL goto GAME_STATE_MARK_CLUSTER else drop block one cell set block_time to max goto GAME_STATE_INPUT_LOOP GAME_STATE_CLEAR_SPECIAL: [nothing drawn, DFA will always shift to next state] if( no cell below ) mark all cells of type above as dead cluster else mark all cells of type below as dead cluster set cluster_time to max goto GAME_STATE_REMOVE_CLUSTER GAME_STATE_REMOVE_CLUSTER: [Draw grid, color old/new clusters] decrease cluster_time if( cluster_time is zero ) remove any special cells remove old cluster and increase score increased cleared_cell_count set level according to cleared_cell_count goto GAME_STATE_MARK_CLUSTER goto GAME_STATE_REMOVE_CLUSTER GAME_STATE_MARK_CLUSTER [Draw grid, color old clusters] mark clusters if( new cluster created ) set cluster_time to max goto GAME_STATE_REMOVE_CLUSTER goto GAME_STATE_GENERATE_BLOCK GAME_STATE_GAME_OVER: [Draw grid] decrease delay_time if( delay_time is zero ) if( highscorer ) goto GAME_STATE_ENTER_NAME goto GAME_STATE_SHOW_STATS goto GAME_STATE_GAME_OVER GAME_STATE_ENTER_NAME: [Draw stats and name] Get keyboard input if( enter pressed ) set delay_time update table goto GAME_STATE_WAIT_START if( string space available ) update name string goto GAME_STATE_ENTER_NAME GAME_STATE_SHOW_STATS: [Draw stats] if( key pressed ) set delay_time goto GAME_STATE_WAIT_START goto GAME_STATE_SHOW_STATS 12/31/00 */ #include"global.h" #include"game.h" #include"sound.h" static int ChainFactor = 1; static int Cluster[32][32]; static int BlockTime = 0; static clock_t SysTimer = 0; static void ClearCluster(void); static BOOL GenerateBlock(void); static BOOL MarkClusters(void); static void MarkSpecialCluster(void); static int PaintCluster(int x, int y, int tile, int *newcluster); static void ResetPlayer(void); /***************************************************************** DropBlock Move block down until floor reached. */ void DropBlock(void) { if( GameState != GAME_STATE_INPUT_LOOP ) return; // Find block position for(; (BlockY > 0) && (Grid[BlockX][BlockY - 1] & CELL_FREE); BlockY--); // Add block to grid Grid[BlockX][BlockY] = BlockT0; Grid[BlockX][BlockY + 1] = BlockT1; // Shift DFA state if( BlockT0 == TILE_SPECIAL || BlockT1 == TILE_SPECIAL ) GameState = GAME_STATE_CLEAR_SPECIAL; else GameState = GAME_STATE_MARK_CLUSTER; BlockTime = 0; } // DropBlock() /***************************************************************** EnterName Add current player's entry to high score table. */ void EnterName(void) { int i, j, k; // Find insert position for(i = 0; i < MAX_HISTORY_LENGTH && History[i].data[HISTORY_SCORE] >= CurrentPlayer.data[HISTORY_SCORE]; i++); if( i >= MAX_HISTORY_LENGTH ) return; // Compute registry entry checksum SetCRC(&CurrentPlayer); // Insert name entry for(j = MAX_HISTORY_LENGTH - 1; j > i; j--) { for(k = 0; k < MAX_NAME_LENGTH_D; k++) History[j].n.data[k] = History[j - 1].n.data[k]; for(k = 0; k < HISTORY_COUNT; k++) History[j].data[k] = History[j - 1].data[k]; } for(k = 0; k < MAX_NAME_LENGTH_D; k++) History[i].n.data[k] = CurrentPlayer.n.data[k]; for(k = 0; k < HISTORY_COUNT; k++) History[i].data[k] = CurrentPlayer.data[k]; // Shift DFA state GameState = GAME_STATE_WAIT_START; GameStateTime = ExpectedFPS * START_DELAY; RedrawAll = TRUE; } // EnterName() /****************************************************************** InitGame Initialize game, load high scores and options. */ BOOL InitGame(void) { DWORD ktype, ksize, kdata, crc; HKEY hkey; char vkey[256]; int i, j; // Reset current player data ResetPlayer(); // Open existing key / create new key if( RegOpenKeyEx(HKEY_CURRENT_USER, REG_KEY_PATH, 0, KEY_EXECUTE, &hkey) != ERROR_SUCCESS ) { LogMessage("Can not open key"); return FALSE; } // Get game data ktype = REG_BINARY; ksize = sizeof(DWORD); if( RegQueryValueEx(hkey, EXEC_COUNT_KEY, NULL, &ktype, (BYTE*)&kdata, &ksize) != ERROR_SUCCESS ) { LogMessage("query failed: " EXEC_COUNT_KEY); ExecCount = 1; RegCloseKey(hkey); return FALSE; } else { ExecCount = kdata + 1; } ktype = REG_BINARY; ksize = sizeof(DWORD); if( RegQueryValueEx(hkey, OPTION_KEY, NULL, &ktype, (BYTE*)&kdata, &ksize) != ERROR_SUCCESS ) { LogMessage("query failed: " OPTION_KEY); GameOptions = OPTION_DEFAULT; RegCloseKey(hkey); return FALSE; } else { GameOptions = kdata; } // Get high score history for(i = 0; i < MAX_HISTORY_LENGTH; i++) { crc = INIT_CRC; for(j = 0; j < MAX_NAME_LENGTH_D; j++) { ksize = sizeof(DWORD); ktype = REG_BINARY; sprintf(vkey, "p%02d_ndata%d", i, j); RegQueryValueEx(hkey, vkey, NULL, &ktype, (BYTE*)&kdata, &ksize); crc ^= (History[i].n.data[j] = kdata); } for(j = 0; j < HISTORY_COUNT; j++) { ksize = sizeof(DWORD); ktype = REG_BINARY; sprintf(vkey, "p%02d_data%d", i, j); RegQueryValueEx(hkey, vkey, NULL, &ktype, (BYTE*)&kdata, &ksize); crc ^= (History[i].data[j] = kdata); } if( crc != 0 ) { LogMessageN("CRC failed for entry %d", i); RegCloseKey(hkey); return FALSE; } } RegCloseKey(hkey); return TRUE; } // InitGame() /************************************************************* MoveBlockLeft Shift current block left. */ void MoveBlockLeft(void) { if( GameState != GAME_STATE_INPUT_LOOP ) return; if( BlockX > 0 ) { if( (Grid[BlockX - 1][BlockY] & CELL_FREE) && (Grid[BlockX - 1][BlockY + 1] & CELL_FREE) ) BlockX--; SoundBlip(); // (sound.cpp) } } // MoveBlockLeft() /************************************************************ MoveBlockRight Shift current block right. */ void MoveBlockRight(void) { if( GameState != GAME_STATE_INPUT_LOOP ) return; if( BlockX < (GRID_WIDTH - 1) ) { if( (Grid[BlockX + 1][BlockY] & CELL_FREE) && (Grid[BlockX + 1][BlockY + 1] & CELL_FREE) ) BlockX++; SoundBlip(); // (sound.cpp) } } // MoveBlockRight() /***************************************************************** ResetGame Reset grid. */ void ResetGame(void) { int i; if( GameState == TERMINAL_STATE ) { GameState = SYS_STATE_INIT; return; } for(i = 0; i < HISTORY_COUNT; CurrentPlayer.data[i++] = 0); GameLevel = 0; GameState = GAME_STATE_WAIT_START; GameStateTime = ExpectedFPS * START_DELAY; RedrawAll = TRUE; FixColumn = -1; FixSpecial = FALSE; AppPaused = FALSE; } // ResetGame() /************************************************************** ResetOptions Reset options and high scores. */ void ResetOptions(void) { int i, j; for(i = 0; i < MAX_HISTORY_LENGTH; i++) { History[i].data[HISTORY_CRC] = INIT_CRC; for(j = 0; j < HISTORY_COUNT - 1; History[i].data[j++] = 0); for(j = 0; j < MAX_NAME_LENGTH_D; History[i].n.data[j++] = 0); } } // ResetOptions() /*************************************************************** RotateBlock Change block orientation. */ void RotateBlock(void) { if( GameState != GAME_STATE_INPUT_LOOP ) return; BlockT0 ^= BlockT1; BlockT1 ^= BlockT0; BlockT0 ^= BlockT1; SoundBlip(); // (sound.cpp) } // RotateBlock() /******************************************************************** SetCRC Compute CRC for player entry. */ void SetCRC(PlayerEntry *player) { DWORD crc; int i; crc = INIT_CRC; for(i = 0; i < MAX_NAME_LENGTH_D; crc ^= player->n.data[i++]); for(i = 0; i < HISTORY_COUNT - 1; crc ^= player->data[i++]); player->data[HISTORY_CRC] = crc; } // SetCRC() /***************************************************************** StartGame Go to GAME_STATE_GENERATE_BLOCK (start game). */ void StartGame(void) { int x, y; // Reset grid for(x = 0; x < GRID_WIDTH; x++) for(y = 0; y <= GRID_HEIGHT; Grid[x][y++] = CELL_FREE); // Reset player scores ResetPlayer(); // Reset game/DFA states FixColumn = -1; FixSpecial = FALSE; GameLevel = 1; RedrawAll = TRUE; GameState = GAME_STATE_GENERATE_BLOCK; AppPaused = FALSE; } // StartGame() /**************************************************************** UninitGame Save highscores and options. */ void UninitGame(void) { DWORD status; char key[128]; HKEY hkey; int i, j; // Open existing key / create new key if( RegCreateKeyEx(HKEY_CURRENT_USER, REG_KEY_PATH, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, &status) != ERROR_SUCCESS ) { LogMessage("Can not open key."); return; } // Write game data if( RegSetValueEx(hkey, EXEC_COUNT_KEY, 0, REG_BINARY, (BYTE*)&ExecCount, sizeof(DWORD)) != ERROR_SUCCESS ) { LogMessage("Set value failed: " EXEC_COUNT_KEY); RegCloseKey(hkey); return; } if( RegSetValueEx(hkey, OPTION_KEY, 0, REG_BINARY, (BYTE*)&GameOptions, sizeof(DWORD)) != ERROR_SUCCESS ) { LogMessage("Set value failed: " OPTION_KEY); RegCloseKey(hkey); return; } // Write player data for(i = 0; i < MAX_HISTORY_LENGTH; i++) { for(j = 0; j < MAX_NAME_LENGTH_D; j++) { sprintf(key, "p%02d_ndata%d", i, j); if( RegSetValueEx(hkey, key, 0, REG_BINARY, (BYTE*)(&(History[i].n.data[j])), sizeof(DWORD)) != ERROR_SUCCESS ) { LogMessageN("set value failed: History[%d]", i); RegCloseKey(hkey); return; } } for(j = 0; j < HISTORY_COUNT; j++) { sprintf(key, "p%02d_data%d", i, j); if( RegSetValueEx(hkey, key, 0, REG_BINARY, (BYTE*)(&(History[i].data[j])), sizeof(DWORD)) != ERROR_SUCCESS ) { LogMessageN("set value failed: History[%d]", i); RegCloseKey(hkey); return; } } } RegCloseKey(hkey); } // UninitGame() /*************************************************************** UpdateState Update DFA state. */ void UpdateState(void) { static char text[1024]; int i; if( AppPaused ) return; if( GameState > 0 && GameState < GAME_STATE_GAME_OVER0) { CurrentPlayer.data[HISTORY_TIME]++; if( (int)CurrentPlayer.data[HISTORY_TIME] >= MaxFrames ) CurrentPlayer.data[HISTORY_TIME] = 0; } switch( GameState ) { case GAME_STATE_WAIT_START: if( GameStateTime-- <= 0 ) { GameStateTime = ExpectedFPS * START_DELAY; GameState = GAME_STATE_SHOW_HISTORY; RedrawAll = TRUE; } break; case GAME_STATE_SHOW_HISTORY: if( GameStateTime-- <= 0 ) { GameStateTime = ExpectedFPS * START_DELAY; GameState = GAME_STATE_WAIT_START; RedrawAll = TRUE; } break; case GAME_STATE_GENERATE_BLOCK: // Always fall through to next state RedrawAll = TRUE; if( GenerateBlock() ) { GameStateTime = ExpectedFPS * 3; GameState = GAME_STATE_GAME_OVER0 + (rand() % 4); AppPaused = FALSE; break; } CurrentPlayer.data[HISTORY_BLOCKS]++; BlockTime = MINIMUM_TIME + (ExpectedFPS / 10) * (9 - GameLevel); GameState = GAME_STATE_INPUT_LOOP; case GAME_STATE_INPUT_LOOP: BlockTime--; if( BlockTime <= 0 ) { if( BlockY == 0 || !(Grid[BlockX][BlockY - 1] & CELL_FREE) ) { Grid[BlockX][BlockY] = BlockT0; Grid[BlockX][BlockY + 1] = BlockT1; if( BlockT0 == TILE_SPECIAL || BlockT1 == TILE_SPECIAL ) GameState = GAME_STATE_CLEAR_SPECIAL; else GameState = GAME_STATE_MARK_CLUSTER; } else { BlockY--; BlockTime = MINIMUM_TIME + (ExpectedFPS/10) * (9 - GameLevel); } } break; case GAME_STATE_CLEAR_SPECIAL: // Always fall through to next state MarkSpecialCluster(); GameState = GAME_STATE_REMOVE_CLUSTER; case GAME_STATE_REMOVE_CLUSTER: GameStateTime--; if( GameStateTime <= 0 ) { ClearCluster(); GameState = GAME_STATE_MARK_CLUSTER; } break; case GAME_STATE_MARK_CLUSTER: if( MarkClusters() ) { GameStateTime = (ExpectedFPS / 10) + 2 * (9 - GameLevel); GameState = GAME_STATE_REMOVE_CLUSTER; break; } GameState = GAME_STATE_GENERATE_BLOCK; break; case GAME_STATE_GAME_OVER0: case GAME_STATE_GAME_OVER1: case GAME_STATE_GAME_OVER2: case GAME_STATE_GAME_OVER3: GameStateTime--; if( GameStateTime <= 0 ) { for(i = 0; i < MAX_HISTORY_LENGTH; i++) { if( CurrentPlayer.data[HISTORY_SCORE] > History[i].data[HISTORY_SCORE] ) break; } if( i < MAX_HISTORY_LENGTH ) { strcpy(CurrentPlayer.n.name, History[i].n.name); GameState = GAME_STATE_ENTER_NAME; } else { GameState = GAME_STATE_SHOW_STATS; } RedrawAll = TRUE; } break; case GAME_STATE_ENTER_NAME: case GAME_STATE_SHOW_STATS: // Wait for keypress break; case SYS_STATE_INIT: LogMessage("Start FPS calibration"); SysTimer = clock(); FrameCount = 0; RedrawAll = TRUE; GameState = SYS_STATE_INIT0; break; case SYS_STATE_INIT0: if( (clock() - SysTimer) > CLOCKS_PER_SEC ) { RedrawAll = TRUE; GameState = SYS_STATE_INIT1; } break; case SYS_STATE_INIT1: if( (clock() - SysTimer) > (CLOCKS_PER_SEC * 2) ) { RedrawAll = TRUE; GameState = SYS_STATE_INIT2; } break; case SYS_STATE_INIT2: if( (clock() - SysTimer) > (CLOCKS_PER_SEC * 3) ) { RedrawAll = TRUE; GameState = SYS_STATE_INIT3; } break; case SYS_STATE_INIT3: if( (clock() - SysTimer) > (CLOCKS_PER_SEC * 4) ) { RedrawAll = TRUE; GameState = SYS_STATE_INIT4; ExpectedFPS = FrameCount / 4; ExpectedFPS = (ExpectedFPS + 1) & -2; MaxFrames = ExpectedFPS * 6000; if( ExpectedFPS < 10 ) ExpectedFPS = 10; LogMessageN("Rendered frames = %d", FrameCount); LogMessageN("Calibrated FPS = %d", ExpectedFPS); } break; case SYS_STATE_INIT4: if( (clock() - SysTimer) > (CLOCKS_PER_SEC * 6) ) ResetGame(); break; case TERMINAL_STATE: break; default: // Undefined state ResetGame(); break; } } // UpdateState() /************************************************************** ClearCluster Clear marked cluster and add to score. */ static void ClearCluster(void) { int size, dscore, x, y, y0; // Shift cells down for(x = dscore = size = 0; x < GRID_WIDTH; x++) { for(y = y0 = 0; y0 < GRID_HEIGHT;) { for(; (y0 < GRID_HEIGHT) && (Grid[x][y0] & CELL_DEAD_CLUSTER); y0++) { size++; if( dscore == 0 ) dscore = 1; else if( (dscore *= 2) > MAX_DELTA_SCORE ) dscore = MAX_DELTA_SCORE; } if( y0 < GRID_HEIGHT ) Grid[x][y++] = Grid[x][y0++]; } if( y < GRID_HEIGHT ) for(; y < GRID_HEIGHT; Grid[x][y++] = CELL_FREE); } // Increase game level by number of cells cleared if( (GameLevel = ((CurrentPlayer.data[HISTORY_CELLS] += size) / CELLS_PER_LEVEL) + 1) > MAX_LEVEL ) GameLevel = MAX_LEVEL; // Increase scores if( (CurrentPlayer.data[HISTORY_SCORE] += ChainFactor * (dscore / 2)) > MAX_SCORE ) CurrentPlayer.data[HISTORY_SCORE] = MAX_SCORE; if( (DWORD)(ChainFactor / 2 + 1) > CurrentPlayer.data[HISTORY_MAX_CHAIN] ) CurrentPlayer.data[HISTORY_MAX_CHAIN] = (DWORD)(ChainFactor / 2 + 1); if( (DWORD)size > CurrentPlayer.data[HISTORY_MAX_CLUSTER] ) CurrentPlayer.data[HISTORY_MAX_CLUSTER] = (DWORD)size; if( dscore ) { CurrentPlayer.data[HISTORY_CLUSTER]++; SoundClear(); // (sound.cpp) } ChainFactor += 2; } // ClearCluster() /************************************************************* GenerateBlock Test for grid overflow and generate random block. */ static BOOL GenerateBlock(void) { // Set block position BlockX = (FixColumn < 0) ? (rand() % GRID_WIDTH) : FixColumn; BlockY = GRID_HEIGHT - 1; if( !(Grid[BlockX][BlockY] & CELL_FREE) ) return TRUE; // Collision -- ame over // Select two distinct block cells BlockT1 = (rand() % 5) + 1; if( !FixSpecial && (rand() % SPECIAL_FREQ) ) { for(BlockT0 = (rand() % 5) + 1; BlockT0 == BlockT1; BlockT0 = (rand() % 5) + 1); } else { BlockT0 = TILE_SPECIAL; SoundSpecial(); // (sound.cpp) } ChainFactor = 1; return FALSE; } // GenerateBlock() /************************************************************** MarkClusters Find clusters in grid. */ static BOOL MarkClusters(void) { int x, y, size; BOOL newcluster, tmpcluster; // Reset clusters for(x = 0; x < GRID_WIDTH; x++) for(y = 0; y < GRID_HEIGHT; Cluster[x][y++] = 0); // Mark all current clusters as dead (ready to be removed) for(x = 0; x < GRID_WIDTH; x++) { for(y = 0; y < GRID_HEIGHT; y++) { if( (Grid[x][y] & (CELL_FREE | CELL_CLUSTER)) == CELL_CLUSTER ) Grid[x][y] |= CELL_DEAD_CLUSTER; } } // Find clusters newcluster = FALSE; for(x = 0; x < GRID_WIDTH; x++) { for(y = 0; y < GRID_HEIGHT; y++) { // Skip empty / marked cells if( (Grid[x][y] & CELL_FREE) || Cluster[x][y] || (Grid[x][y] & CELL_DEAD_CLUSTER) ) continue; // Mark cluster tmpcluster = TRUE; size = PaintCluster(x, y, Grid[x][y] & CELL_TYPE, &tmpcluster); if( size <= 1 ) { tmpcluster = FALSE; Grid[x][y] &= ~CELL_CLUSTER; } if( tmpcluster ) newcluster = TRUE; } } // If no new cluster found, unmark all dead clusters if( !newcluster ) { for(x = 0; x < GRID_WIDTH; x++) for(y = 0; y < GRID_HEIGHT; Grid[x][y++] &= ~CELL_DEAD_CLUSTER); return FALSE; } return TRUE; } // MarkClusters() /******************************************************** MarkSpecialCluster Mark all clusters of same tile. */ static void MarkSpecialCluster(void) { int tile, x, y; // Select tile type (cell below, or cell above if no cell below) if( BlockT1 == TILE_SPECIAL ) tile = BlockT0; else tile = (BlockY > 0) ? (Grid[BlockX][BlockY - 1] & CELL_TYPE) : BlockT1; // Mark all cells of same type for(x = 0; x < GRID_WIDTH; x++) { for(y = 0; y < GRID_HEIGHT; y++) { if( Grid[x][y] & CELL_FREE ) continue; if( (Grid[x][y] & CELL_TYPE) == tile || (Grid[x][y] & CELL_TYPE) == TILE_SPECIAL ) Grid[x][y] |= CELL_CLUSTER | CELL_DEAD_CLUSTER; } } // Set delay time GameStateTime = (ExpectedFPS / 10) + 2 * (9 - GameLevel); } // MarkSpecialCluster() /************************************************************** PaintCluster Mark cluster and return its size. */ static int PaintCluster(int x, int y, int tile, BOOL *newcluster) { int size; // Skip all painted/empty cells if( Cluster[x][y] || (Grid[x][y] & CELL_FREE) ) return 0; // Skip all nonmatching neighboring cells if( (Grid[x][y] & CELL_TYPE) != tile ) return 0; // If current cluster connects to a previously marked cluster, then // current cluster is not new cluster if( Grid[x][y] & CELL_DEAD_CLUSTER ) *newcluster = FALSE; // Recursively expand and mark clusters size = 0; Cluster[x][y] = 1; Grid[x][y] |= CELL_CLUSTER; if( x > 0 ) size = PaintCluster(x - 1, y, tile, newcluster); if( x < GRID_WIDTH - 1 ) size += PaintCluster(x + 1, y, tile, newcluster); if( y > 0 ) size += PaintCluster(x, y - 1, tile, newcluster); if( y < GRID_HEIGHT - 1 ) size += PaintCluster(x, y + 1, tile, newcluster); return 1 + size; } // PaintCluster() /*************************************************************** ResetPlayer Reset current player data. */ static void ResetPlayer(void) { int i; for(i = 0; i < HISTORY_COUNT; CurrentPlayer.data[i++] = 0); for(i = 0; i < MAX_NAME_LENGTH_D; CurrentPlayer.n.data[i++] = 0); strcpy(CurrentPlayer.n.name, "anonymous"); } // ResetPlayer() char *GameObjTime = __TIME__ " " __DATE__; int GameObjLines = __LINE__;