static void shuffle( V& vector ) { #ifdef CATCH_CPP14_OR_GREATER std::shuffle( vector.begin(), vector.end(), RandomNumberGenerator() ); #else std::random_shuffle( vector.begin(), vector.end(), RandomNumberGenerator() ); #endif }
void BinarySwitch::build(unsigned start, bool hardStart, unsigned end) { if (BinarySwitchInternal::verbose) dataLog("Building with start = ", start, ", hardStart = ", hardStart, ", end = ", end, "\n"); auto append = [&] (const BranchCode& code) { if (BinarySwitchInternal::verbose) dataLog("==> ", code, "\n"); m_branches.append(code); }; unsigned size = end - start; RELEASE_ASSERT(size); // This code uses some random numbers to keep things balanced. It's important to keep in mind // that this does not improve average-case throughput under the assumption that all cases fire // with equal probability. It just ensures that there will not be some switch structure that // when combined with some input will always produce pathologically good or pathologically bad // performance. const unsigned leafThreshold = 3; if (size <= leafThreshold) { if (BinarySwitchInternal::verbose) dataLog("It's a leaf.\n"); // It turns out that for exactly three cases or less, it's better to just compare each // case individually. This saves 1/6 of a branch on average, and up to 1/3 of a branch in // extreme cases where the divide-and-conquer bottoms out in a lot of 3-case subswitches. // // This assumes that we care about the cost of hitting some case more than we care about // bottoming out in a default case. I believe that in most places where we use switch // statements, we are more likely to hit one of the cases than we are to fall through to // default. Intuitively, if we wanted to improve the performance of default, we would // reduce the value of leafThreshold to 2 or even to 1. See below for a deeper discussion. bool allConsecutive = false; if ((hardStart || (start && m_cases[start - 1].value == m_cases[start].value - 1)) && start + size < m_cases.size() && m_cases[start + size - 1].value == m_cases[start + size].value - 1) { allConsecutive = true; for (unsigned i = 0; i < size - 1; ++i) { if (m_cases[start + i].value + 1 != m_cases[start + i + 1].value) { allConsecutive = false; break; } } } if (BinarySwitchInternal::verbose) dataLog("allConsecutive = ", allConsecutive, "\n"); Vector<unsigned, 3> localCaseIndices; for (unsigned i = 0; i < size; ++i) localCaseIndices.append(start + i); std::shuffle( localCaseIndices.begin(), localCaseIndices.end(), RandomNumberGenerator(m_weakRandom)); for (unsigned i = 0; i < size - 1; ++i) { append(BranchCode(NotEqualToPush, localCaseIndices[i])); append(BranchCode(ExecuteCase, localCaseIndices[i])); append(BranchCode(Pop)); } if (!allConsecutive) append(BranchCode(NotEqualToFallThrough, localCaseIndices.last())); append(BranchCode(ExecuteCase, localCaseIndices.last())); return; } if (BinarySwitchInternal::verbose) dataLog("It's not a leaf.\n"); // There are two different strategies we could consider here: // // Isolate median and split: pick a median and check if the comparison value is equal to it; // if so, execute the median case. Otherwise check if the value is less than the median, and // recurse left or right based on this. This has two subvariants: we could either first test // equality for the median and then do the less-than, or we could first do the less-than and // then check equality on the not-less-than path. // // Ignore median and split: do a less-than comparison on a value that splits the cases in two // equal-sized halves. Recurse left or right based on the comparison. Do not test for equality // against the median (or anything else); let the recursion handle those equality comparisons // once we bottom out in a list that case 3 cases or less (see above). // // I'll refer to these strategies as Isolate and Ignore. I initially believed that Isolate // would be faster since it leads to less branching for some lucky cases. It turns out that // Isolate is almost a total fail in the average, assuming all cases are equally likely. How // bad Isolate is depends on whether you believe that doing two consecutive branches based on // the same comparison is cheaper than doing the compare/branches separately. This is // difficult to evaluate. For small immediates that aren't blinded, we just care about // avoiding a second compare instruction. For large immediates or when blinding is in play, we // also care about the instructions used to materialize the immediate a second time. Isolate // can help with both costs since it involves first doing a < compare+branch on some value, // followed by a == compare+branch on the same exact value (or vice-versa). Ignore will do a < // compare+branch on some value, and then the == compare+branch on that same value will happen // much later. // // To evaluate these costs, I wrote the recurrence relation for Isolate and Ignore, assuming // that ComparisonCost is the cost of a compare+branch and ChainedComparisonCost is the cost // of a compare+branch on some value that you've just done another compare+branch for. These // recurrence relations compute the total cost incurred if you executed the switch statement // on each matching value. So the average cost of hitting some case can be computed as // Isolate[n]/n or Ignore[n]/n, respectively for the two relations. // // Isolate[1] = ComparisonCost // Isolate[2] = (2 + 1) * ComparisonCost // Isolate[3] = (3 + 2 + 1) * ComparisonCost // Isolate[n_] := With[ // {medianIndex = Floor[n/2] + If[EvenQ[n], RandomInteger[], 1]}, // ComparisonCost + ChainedComparisonCost + // (ComparisonCost * (medianIndex - 1) + Isolate[medianIndex - 1]) + // (2 * ComparisonCost * (n - medianIndex) + Isolate[n - medianIndex])] // // Ignore[1] = ComparisonCost // Ignore[2] = (2 + 1) * ComparisonCost // Ignore[3] = (3 + 2 + 1) * ComparisonCost // Ignore[n_] := With[ // {medianIndex = If[EvenQ[n], n/2, Floor[n/2] + RandomInteger[]]}, // (medianIndex * ComparisonCost + Ignore[medianIndex]) + // ((n - medianIndex) * ComparisonCost + Ignore[n - medianIndex])] // // This does not account for the average cost of hitting the default case. See further below // for a discussion of that. // // It turns out that for ComparisonCost = 1 and ChainedComparisonCost = 1, Ignore is always // better than Isolate. If we assume that ChainedComparisonCost = 0, then Isolate wins for // switch statements that have 20 cases or fewer, though the margin of victory is never large // - it might sometimes save an average of 0.3 ComparisonCost. For larger switch statements, // we see divergence between the two with Ignore winning. This is of course rather // unrealistic since the chained comparison is never free. For ChainedComparisonCost = 0.5, we // see Isolate winning for 10 cases or fewer, by maybe 0.2 ComparisonCost. Again we see // divergence for large switches with Ignore winning, for example if a switch statement has // 100 cases then Ignore saves one branch on average. // // Our current JIT backends don't provide for optimization for chained comparisons, except for // reducing the code for materializing the immediate if the immediates are large or blinding // comes into play. Probably our JIT backends live somewhere north of // ChainedComparisonCost = 0.5. // // This implies that using the Ignore strategy is likely better. If we wanted to incorporate // the Isolate strategy, we'd want to determine the switch size threshold at which the two // cross over and then use Isolate for switches that are smaller than that size. // // The average cost of hitting the default case is similar, but involves a different cost for // the base cases: you have to assume that you will always fail each branch. For the Ignore // strategy we would get this recurrence relation; the same kind of thing happens to the // Isolate strategy: // // Ignore[1] = ComparisonCost // Ignore[2] = (2 + 2) * ComparisonCost // Ignore[3] = (3 + 3 + 3) * ComparisonCost // Ignore[n_] := With[ // {medianIndex = If[EvenQ[n], n/2, Floor[n/2] + RandomInteger[]]}, // (medianIndex * ComparisonCost + Ignore[medianIndex]) + // ((n - medianIndex) * ComparisonCost + Ignore[n - medianIndex])] // // This means that if we cared about the default case more, we would likely reduce // leafThreshold. Reducing it to 2 would reduce the average cost of the default case by 1/3 // in the most extreme cases (num switch cases = 3, 6, 12, 24, ...). But it would also // increase the average cost of taking one of the non-default cases by 1/3. Typically the // difference is 1/6 in either direction. This makes it a very simple trade-off: if we believe // that the default case is more important then we would want leafThreshold to be 2, and the // default case would become 1/6 faster on average. But we believe that most switch statements // are more likely to take one of the cases than the default, so we use leafThreshold = 3 // and get a 1/6 speed-up on average for taking an explicit case. unsigned medianIndex = (start + end) / 2; if (BinarySwitchInternal::verbose) dataLog("medianIndex = ", medianIndex, "\n"); // We want medianIndex to point to the thing we will do a less-than compare against. We want // this less-than compare to split the current sublist into equal-sized sublists, or // nearly-equal-sized with some randomness if we're in the odd case. With the above // calculation, in the odd case we will have medianIndex pointing at either the element we // want or the element to the left of the one we want. Consider the case of five elements: // // 0 1 2 3 4 // // start will be 0, end will be 5. The average is 2.5, which rounds down to 2. If we do // value < 2, then we will split the list into 2 elements on the left and three on the right. // That's pretty good, but in this odd case we'd like to at random choose 3 instead to ensure // that we don't become unbalanced on the right. This does not improve throughput since one // side will always get shafted, and that side might still be odd, in which case it will also // have two sides and one of them will get shafted - and so on. We just want to avoid // deterministic pathologies. // // In the even case, we will always end up pointing at the element we want: // // 0 1 2 3 // // start will be 0, end will be 4. So, the average is 2, which is what we'd like. if (size & 1) { RELEASE_ASSERT(medianIndex - start + 1 == end - medianIndex); medianIndex += m_weakRandom.getUint32() & 1; } else RELEASE_ASSERT(medianIndex - start == end - medianIndex); RELEASE_ASSERT(medianIndex > start); RELEASE_ASSERT(medianIndex + 1 < end); if (BinarySwitchInternal::verbose) dataLog("fixed medianIndex = ", medianIndex, "\n"); append(BranchCode(LessThanToPush, medianIndex)); build(medianIndex, true, end); append(BranchCode(Pop)); build(start, hardStart, medianIndex); }
/// Updates the entire overworld for the elapsed time. /// @param[in] elapsed_time - The elapsed time for which to update the world. /// @param[in,out] input_controller - The controller supplying player input. /// @param[in,out] camera - The camera defining the viewable region of the overworld. /// @param[out] message_for_text_box - The message to display in the main text box, /// if one needs to start being displayed; empty if no new message needs to be displayed. void GameplayState::UpdateOverworld( const sf::Time& elapsed_time, INPUT_CONTROL::KeyboardInputController& input_controller, GRAPHICS::Camera& camera, std::string& message_for_text_box) { // INDICATE THAT NO MESSAGE NEEDS TO BE DISPLAYED YET IN THE TEXT BOX. message_for_text_box.clear(); MATH::FloatRectangle camera_bounds = camera.ViewBounds; MATH::Vector2f camera_view_center = camera_bounds.GetCenterPosition(); MAPS::TileMap* current_tile_map = Overworld->GetTileMap(camera_view_center.X, camera_view_center.Y); assert(current_tile_map); // CHECK IF THE PRIMARY ACTION BUTTON WAS PRESSED. if (input_controller.ButtonDown(INPUT_CONTROL::KeyboardInputController::PRIMARY_ACTION_KEY)) { // SWING THE PLAYER'S AXE. // A new axe swing may not be created if the player's // axe is already being swung. std::shared_ptr<EVENTS::AxeSwingEvent> axe_swing = Overworld->NoahPlayer->SwingAxe(); bool axe_swing_occurred = (nullptr != axe_swing); if (axe_swing_occurred) { // Allow the axe to collide with other objects. Overworld->AxeSwings.push_back(axe_swing); } } // CHECK IF THE PLAYER IS ALLOWED TO MOVE. // Noah can't move while the axe is swinging. // Movement is prevented to have the axe's position remain attached to Noah. // We need to keep track if Noah moved this frame so that we can stop any walking animations for him if he didn't move. bool noah_moved_this_frame = false; bool axe_is_swinging = (nullptr != Overworld->NoahPlayer->Inventory->Axe) && Overworld->NoahPlayer->Inventory->Axe->IsSwinging(); bool player_movement_allowed = (!axe_is_swinging); if (player_movement_allowed) { // MOVE NOAH IN RESPONSE TO USER INPUT. const float PLAYER_POSITION_ADJUSTMENT_FOR_SCROLLING_IN_PIXELS = 8.0f; MATH::Vector2f old_noah_position = Overworld->NoahPlayer->GetWorldPosition(); if (input_controller.ButtonDown(INPUT_CONTROL::KeyboardInputController::UP_KEY)) { // TRACK NOAH AS MOVING THIS FRAME. noah_moved_this_frame = true; // START NOAH WALKING UP. Overworld->NoahPlayer->BeginWalking(CORE::Direction::UP, OBJECTS::Noah::WALK_BACK_ANIMATION_NAME); // MOVE NOAH WHILE HANDLING COLLISIONS. MATH::Vector2f new_position = COLLISION::CollisionDetectionAlgorithms::MoveObject( Overworld->NoahPlayer->GetWorldBoundingBox(), CORE::Direction::UP, OBJECTS::Noah::MOVE_SPEED_IN_PIXELS_PER_SECOND, elapsed_time, *Overworld); Overworld->NoahPlayer->SetWorldPosition(new_position); // CHECK IF NOAH MOVED OUT OF THE CAMERA'S VIEW. MATH::FloatRectangle noah_world_bounding_box = Overworld->NoahPlayer->GetWorldBoundingBox(); float camera_top_y_position = camera_bounds.GetTopYPosition(); bool player_moved_out_of_view = (noah_world_bounding_box.GetTopYPosition() < camera_top_y_position); if (player_moved_out_of_view) { // CHECK IF A TOP TILE MAP EXISTS FOR NOAH TO MOVE TO. unsigned int top_tile_map_row_index = current_tile_map->OverworldRowIndex - 1; unsigned int top_tile_map_column_index = current_tile_map->OverworldColumnIndex; MAPS::TileMap* top_tile_map = Overworld->GetTileMap( top_tile_map_row_index, top_tile_map_column_index); bool top_tile_map_exists = (nullptr != top_tile_map); if (top_tile_map_exists) { // MOVE NOAH A FEW MORE PIXELS UP SO THAT HE WILL BE MORE VISIBLE ON THE NEW MAP. MATH::Vector2f noah_world_position = Overworld->NoahPlayer->GetWorldPosition(); noah_world_position.Y -= PLAYER_POSITION_ADJUSTMENT_FOR_SCROLLING_IN_PIXELS; Overworld->NoahPlayer->SetWorldPosition(noah_world_position); // START SCROLLING TO THE TOP TILE MAP. MATH::Vector2f scroll_start_position = current_tile_map->GetCenterWorldPosition(); MATH::Vector2f scroll_end_position = top_tile_map->GetCenterWorldPosition(); camera.StartScrolling(scroll_start_position, scroll_end_position); // DISABLE PLAYER MOVEMENT WHILE SCROLLING IS OCCURRING. input_controller.DisableInput(); } else { // KEEP NOAH WITHIN THE BOUNDS OF THE CURRENT TILE MAP. // Since there is no top tile map to scroll to, this will keep Noah on-screen. MATH::FloatRectangle tile_map_bounding_box = current_tile_map->GetWorldBoundingBox(); float tile_map_top_boundary = tile_map_bounding_box.GetTopYPosition(); // To keep Noah completely on screen, his center position should be half // his height below the top tile map boundary. MATH::Vector2f noah_world_position = old_noah_position; float noah_half_height = noah_world_bounding_box.GetHeight() / 2.0f; noah_world_position.Y = tile_map_top_boundary + noah_half_height; Overworld->NoahPlayer->SetWorldPosition(noah_world_position); } } } if (input_controller.ButtonDown(INPUT_CONTROL::KeyboardInputController::DOWN_KEY)) { // TRACK NOAH AS MOVING THIS FRAME. noah_moved_this_frame = true; // START NOAH WALKING DOWN. Overworld->NoahPlayer->BeginWalking(CORE::Direction::DOWN, OBJECTS::Noah::WALK_FRONT_ANIMATION_NAME); // MOVE NOAH WHILE HANDLING COLLISIONS. MATH::Vector2f new_position = COLLISION::CollisionDetectionAlgorithms::MoveObject( Overworld->NoahPlayer->GetWorldBoundingBox(), CORE::Direction::DOWN, OBJECTS::Noah::MOVE_SPEED_IN_PIXELS_PER_SECOND, elapsed_time, *Overworld); Overworld->NoahPlayer->SetWorldPosition(new_position); // CHECK IF NOAH MOVED OUT OF THE CAMERA'S VIEW. MATH::FloatRectangle noah_world_bounding_box = Overworld->NoahPlayer->GetWorldBoundingBox(); float camera_bottom_y_position = camera_bounds.GetBottomYPosition(); bool player_moved_out_of_view = (noah_world_bounding_box.GetBottomYPosition() > camera_bottom_y_position); if (player_moved_out_of_view) { // CHECK IF A BOTTOM TILE MAP EXISTS FOR NOAH TO MOVE TO. unsigned int bottom_tile_map_row_index = current_tile_map->OverworldRowIndex + 1; unsigned int bottom_tile_map_column_index = current_tile_map->OverworldColumnIndex; MAPS::TileMap* bottom_tile_map = Overworld->GetTileMap( bottom_tile_map_row_index, bottom_tile_map_column_index); bool bottom_tile_map_exists = (nullptr != bottom_tile_map); if (bottom_tile_map_exists) { // MOVE NOAH A FEW MORE PIXELS DOWN SO THAT HE WILL BE MORE VISIBLE ON THE NEW MAP. MATH::Vector2f noah_world_position = Overworld->NoahPlayer->GetWorldPosition(); noah_world_position.Y += PLAYER_POSITION_ADJUSTMENT_FOR_SCROLLING_IN_PIXELS; Overworld->NoahPlayer->SetWorldPosition(noah_world_position); // START SCROLLING TO THE BOTTOM TILE MAP. MATH::Vector2f scroll_start_position = current_tile_map->GetCenterWorldPosition(); MATH::Vector2f scroll_end_position = bottom_tile_map->GetCenterWorldPosition(); camera.StartScrolling(scroll_start_position, scroll_end_position); // DISABLE PLAYER MOVEMENT WHILE SCROLLING IS OCCURRING. input_controller.DisableInput(); } else { // KEEP NOAH WITHIN THE BOUNDS OF THE CURRENT TILE MAP. // Since there is no bottom tile map to scroll to, this will keep Noah on-screen. MATH::FloatRectangle tile_map_bounding_box = current_tile_map->GetWorldBoundingBox(); float tile_map_bottom_boundary = tile_map_bounding_box.GetBottomYPosition(); // To keep Noah completely on screen, his center position should be half // his height above the bottom tile map boundary. MATH::Vector2f noah_world_position = old_noah_position; float noah_half_height = Overworld->NoahPlayer->GetWorldBoundingBox().GetHeight() / 2.0f; noah_world_position.Y = tile_map_bottom_boundary - noah_half_height; Overworld->NoahPlayer->SetWorldPosition(noah_world_position); } } } if (input_controller.ButtonDown(INPUT_CONTROL::KeyboardInputController::LEFT_KEY)) { // TRACK NOAH AS MOVING THIS FRAME. noah_moved_this_frame = true; // START NOAH WALKING LEFT. Overworld->NoahPlayer->BeginWalking(CORE::Direction::LEFT, OBJECTS::Noah::WALK_LEFT_ANIMATION_NAME); // MOVE NOAH WHILE HANDLING COLLISIONS. MATH::Vector2f new_position = COLLISION::CollisionDetectionAlgorithms::MoveObject( Overworld->NoahPlayer->GetWorldBoundingBox(), CORE::Direction::LEFT, OBJECTS::Noah::MOVE_SPEED_IN_PIXELS_PER_SECOND, elapsed_time, *Overworld); Overworld->NoahPlayer->SetWorldPosition(new_position); // CHECK IF NOAH MOVED OUT OF THE CAMERA'S VIEW. MATH::FloatRectangle noah_world_bounding_box = Overworld->NoahPlayer->GetWorldBoundingBox(); float camera_left_x_position = camera_bounds.GetLeftXPosition(); bool player_moved_out_of_view = (noah_world_bounding_box.GetLeftXPosition() < camera_left_x_position); if (player_moved_out_of_view) { // CHECK IF A LEFT TILE MAP EXISTS FOR NOAH TO MOVE TO. unsigned int left_tile_map_row_index = current_tile_map->OverworldRowIndex; unsigned int left_tile_map_column_index = current_tile_map->OverworldColumnIndex - 1; MAPS::TileMap* left_tile_map = Overworld->GetTileMap( left_tile_map_row_index, left_tile_map_column_index); bool left_tile_map_exists = (nullptr != left_tile_map); if (left_tile_map_exists) { // MOVE NOAH A FEW MORE PIXELS LEFT SO THAT HE WILL BE MORE VISIBLE ON THE NEW MAP. MATH::Vector2f noah_world_position = Overworld->NoahPlayer->GetWorldPosition(); noah_world_position.X -= PLAYER_POSITION_ADJUSTMENT_FOR_SCROLLING_IN_PIXELS; Overworld->NoahPlayer->SetWorldPosition(noah_world_position); // START SCROLLING TO THE LEFT TILE MAP. MATH::Vector2f scroll_start_position = current_tile_map->GetCenterWorldPosition(); MATH::Vector2f scroll_end_position = left_tile_map->GetCenterWorldPosition(); camera.StartScrolling(scroll_start_position, scroll_end_position); // DISABLE PLAYER MOVEMENT WHILE SCROLLING IS OCCURRING. input_controller.DisableInput(); } else { // KEEP NOAH WITHIN THE BOUNDS OF THE CURRENT TILE MAP. // Since there is no left tile map to scroll to, this will keep Noah on-screen. MATH::FloatRectangle tile_map_bounding_box = current_tile_map->GetWorldBoundingBox(); float tile_map_left_boundary = tile_map_bounding_box.GetLeftXPosition(); // To keep Noah completely on screen, his center position should be half // his width to the right of the left tile map boundary. MATH::Vector2f noah_world_position = old_noah_position; float noah_half_width = Overworld->NoahPlayer->GetWorldBoundingBox().GetWidth() / 2.0f; noah_world_position.X = tile_map_left_boundary + noah_half_width; Overworld->NoahPlayer->SetWorldPosition(noah_world_position); } } } if (input_controller.ButtonDown(INPUT_CONTROL::KeyboardInputController::RIGHT_KEY)) { // TRACK NOAH AS MOVING THIS FRAME. noah_moved_this_frame = true; // START NOAH WALKING RIGHT. Overworld->NoahPlayer->BeginWalking(CORE::Direction::RIGHT, OBJECTS::Noah::WALK_RIGHT_ANIMATION_NAME); // MOVE NOAH WHILE HANDLING COLLISIONS. MATH::Vector2f new_position = COLLISION::CollisionDetectionAlgorithms::MoveObject( Overworld->NoahPlayer->GetWorldBoundingBox(), CORE::Direction::RIGHT, OBJECTS::Noah::MOVE_SPEED_IN_PIXELS_PER_SECOND, elapsed_time, *Overworld); Overworld->NoahPlayer->SetWorldPosition(new_position); // CHECK IF NOAH MOVED OUT OF THE CAMERA'S VIEW. MATH::FloatRectangle noah_world_bounding_box = Overworld->NoahPlayer->GetWorldBoundingBox(); float camera_right_x_position = camera_bounds.GetRightXPosition(); bool player_moved_out_of_view = (noah_world_bounding_box.GetRightXPosition() > camera_right_x_position); if (player_moved_out_of_view) { // CHECK IF A RIGHT TILE MAP EXISTS FOR NOAH TO MOVE TO. unsigned int right_tile_map_row_index = current_tile_map->OverworldRowIndex; unsigned int right_tile_map_column_index = current_tile_map->OverworldColumnIndex + 1; MAPS::TileMap* right_tile_map = Overworld->GetTileMap( right_tile_map_row_index, right_tile_map_column_index); bool right_tile_map_exists = (nullptr != right_tile_map); if (right_tile_map_exists) { // MOVE NOAH A FEW MORE PIXELS RIGHT SO THAT HE WILL BE MORE VISIBLE ON THE NEW MAP. MATH::Vector2f noah_world_position = Overworld->NoahPlayer->GetWorldPosition(); noah_world_position.X += PLAYER_POSITION_ADJUSTMENT_FOR_SCROLLING_IN_PIXELS; Overworld->NoahPlayer->SetWorldPosition(noah_world_position); // START SCROLLING TO THE RIGHT TILE MAP. MATH::Vector2f scroll_start_position = current_tile_map->GetCenterWorldPosition(); MATH::Vector2f scroll_end_position = right_tile_map->GetCenterWorldPosition(); camera.StartScrolling(scroll_start_position, scroll_end_position); // DISABLE PLAYER MOVEMENT WHILE SCROLLING IS OCCURRING. input_controller.DisableInput(); } else { // KEEP NOAH WITHIN THE BOUNDS OF THE CURRENT TILE MAP. // Since there is no right tile map to scroll to, this will keep Noah on-screen. MATH::FloatRectangle tile_map_bounding_box = current_tile_map->GetWorldBoundingBox(); float tile_map_right_boundary = tile_map_bounding_box.GetRightXPosition(); // To keep Noah completely on screen, his center position should be half // his width to the left of the right tile map boundary. MATH::Vector2f noah_world_position = old_noah_position; float noah_half_width = Overworld->NoahPlayer->GetWorldBoundingBox().GetWidth() / 2.0f; noah_world_position.X = tile_map_right_boundary - noah_half_width; Overworld->NoahPlayer->SetWorldPosition(noah_world_position); } } } } // CHECK IF NOAH MOVED THIS FRAME. if (noah_moved_this_frame) { // UPDATE NOAH'S ANIMATION. Overworld->NoahPlayer->Sprite.Update(elapsed_time); // BUILD A PIECE OF THE ARK IF NOAH STEPPED ONTO AN APPROPRIATE SPOT. MATH::Vector2f noah_world_position = Overworld->NoahPlayer->GetWorldPosition(); MAPS::TileMap* tile_map_underneath_noah = Overworld->GetTileMap(noah_world_position.X, noah_world_position.Y); assert(tile_map_underneath_noah); // An ark piece only needs to be built once. OBJECTS::ArkPiece* ark_piece = tile_map_underneath_noah->GetArkPieceAtWorldPosition(noah_world_position); bool ark_piece_can_be_built = (ark_piece && !ark_piece->Built); if (ark_piece_can_be_built) { /// @todo Check if Noah has wood. // BUILD THE ARK PIECE. ark_piece->Built = true; // When building an ark piece, a dust cloud should appear. std::shared_ptr<GRAPHICS::Texture> dust_cloud_texture = Assets->GetTexture(RESOURCES::DUST_CLOUD_TEXTURE_ID); assert(dust_cloud_texture); OBJECTS::DustCloud dust_cloud(dust_cloud_texture); // The dust cloud should be positioned over the ark piece. MATH::Vector2f dust_cloud_center_world_position = ark_piece->Sprite.GetWorldPosition(); dust_cloud.Sprite.SetWorldPosition(dust_cloud_center_world_position); // The dust cloud should start animating immediately. dust_cloud.Sprite.Play(); // The dust cloud needs to be added to the tile map so that it gets updated. tile_map_underneath_noah->DustClouds.push_back(dust_cloud); /// @todo Play sound effect when building ark piece. } } else { // STOP NOAH'S ANIMATION FROM PLAYING SINCE THE PLAYER DIDN'T MOVE THIS FRAME. Overworld->NoahPlayer->Sprite.ResetAnimation(); } // UPDATE THE CAMERA BASED ON SCROLLING. if (camera.IsScrolling) { // SCROLL BASED ON THE ELAPSED FRAME TIME. camera.Scroll(elapsed_time); // CHECK IF SCROLLING HAS FINISHED. bool scrolling_finished = !camera.IsScrolling; if (scrolling_finished) { // RE-ENABLE PLAYER INPUT. input_controller.EnableInput(); } } else { // POSITION THE CAMERA TO FOCUS ON THE CENTER OF THE CURRENT TILE MAP. MATH::Vector2f center_world_position = current_tile_map->GetCenterWorldPosition(); camera.SetCenter(center_world_position); } // HANDLE OTHER COLLISIONS. COLLISION::CollisionDetectionAlgorithms::HandleAxeSwings(*Overworld, Overworld->AxeSwings, *Assets); for (auto wood_logs = current_tile_map->WoodLogs.begin(); wood_logs != current_tile_map->WoodLogs.end();) { // CHECK IF THE WOOD LOGS INTERSECT WITH NOAH. MATH::FloatRectangle wood_log_bounding_box = wood_logs->GetWorldBoundingBox(); MATH::FloatRectangle noah_bounding_box = Overworld->NoahPlayer->GetWorldBoundingBox(); bool noah_collided_with_wood_logs = noah_bounding_box.Contains( wood_log_bounding_box.GetCenterXPosition(), wood_log_bounding_box.GetCenterYPosition()); if (noah_collided_with_wood_logs) { // ADD THE WOOD TO NOAH'S INVENTORY. // The logs can have a random amount of wood. unsigned int MIN_WOOD_COUNT = 1; unsigned int MAX_WOOD_COUNT = 3; unsigned int random_number_for_wood = RandomNumberGenerator(); unsigned int random_wood_count = (random_number_for_wood % MAX_WOOD_COUNT) + MIN_WOOD_COUNT; Overworld->NoahPlayer->Inventory->AddWood(random_wood_count); // REMOVE THE WOOD LOGS SINCE THEY'VE BEEN COLLECTED BY NOAH. wood_logs = current_tile_map->WoodLogs.erase(wood_logs); // SEE IF A BIBLE VERSE SHOULD BE COLLECTED ALONG WITH THE WOOD. // There should be a random chance that a Bible verse can be collected. const unsigned int EVENLY_DIVISIBLE = 0; const unsigned int BIBLE_VERSE_EXISTS_IF_DIVISIBLE_BY_THIS = 2; unsigned int random_number_for_bible_verse = RandomNumberGenerator(); bool bible_verse_exists_with_wood = ((random_number_for_bible_verse % BIBLE_VERSE_EXISTS_IF_DIVISIBLE_BY_THIS) == EVENLY_DIVISIBLE); if (bible_verse_exists_with_wood) { // CHECK IF ANY BIBLE VERSES REMAIN. // This check helps protect against a mod by 0 below. bool bible_verses_remain_to_be_found = !BibleVersesLeftToFind.empty(); if (bible_verses_remain_to_be_found) { // PLAY THE SOUND EFFECT FOR COLLECTING A BIBLE VERSE. std::shared_ptr<AUDIO::SoundEffect> collected_bible_verse_sound = Assets->GetSound(RESOURCES::COLLECT_BIBLE_VERSE_SOUND_ID); bool collect_bible_verse_sound_loaded = (nullptr != collected_bible_verse_sound); if (collect_bible_verse_sound_loaded) { collected_bible_verse_sound->Play(); } // SELECT A RANDOM BIBLE VERSE. unsigned int random_bible_verse_index = RandomNumberGenerator() % BibleVersesLeftToFind.size(); auto bible_verse = BibleVersesLeftToFind.begin() + random_bible_verse_index; // ADD THE BIBLE VERSE TO THE PLAYER'S INVENTORY. Overworld->NoahPlayer->Inventory->BibleVerses.insert(*bible_verse); // POPULATE THE MESSAGE TO DISPLAY IN THE MAIN TEXT BOX. message_for_text_box = "You got a Bible verse!\n" + bible_verse->ToString(); // REMOVE THE VERSE SINCE IT HAS BEEN FOUND. BibleVersesLeftToFind.erase(bible_verse); } } } else { // MOVE TO CHECKING COLLISIONS WITH THE NEXT SET OF WOOD LOGS. ++wood_logs; } } }