indexing description: "[ Brick Breaker Level Scenes ]" date: "$Date$" revision: "$Revision$" class BB_LEVEL_SCENE inherit BB_CONSTANTS EM_SCENE redefine run, uninitialize_scene, handle_update_event, handle_key_down_event, handle_key_up_event, handle_joystick_axis_event, handle_joystick_button_down_event end EM_SHARED_SUBSYSTEMS export {NONE} all end EM_SHARED_STANDARD_FONTS export {NONE} all end EM_SHARED_BITMAP_FACTORY export {NONE} all end EM_TIME_SINGLETON MATH_CONST export {NONE} all end SHARED_BB_SETTINGS BB_SHARED_AUDIO export {NONE} all end create make_from_level, make_from_first_level, make_from_savegame feature {NONE} -- Initialisation make is -- create level do level_set := bb_settings.level_set.out if bb_settings.device.is_equal ("Joystick") then create joystick.make_for_device (0) end if bb_settings.difficulty.is_equal ("Foolproof") then difficulty := Difficulty_foolproof lifes := Start_lifes_foolproof elseif bb_settings.difficulty.is_equal ("Easy") then difficulty := Difficulty_easy lifes := Start_lifes_easy elseif bb_settings.difficulty.is_equal ("Normal") then difficulty := Difficulty_normal lifes := Start_lifes_normal elseif bb_settings.difficulty.is_equal ("Hard") then difficulty := Difficulty_hard lifes := Start_lifes_hard elseif bb_settings.difficulty.is_equal ("Insane") then difficulty := Difficulty_insane lifes := Start_lifes_insane else difficulty := Difficulty_normal lifes := Start_lifes_normal end initialize_level read_level_list score := 0 last_score_extra_life := 0 video_subsystem.hide_cursor end make_from_level (a_level: STRING) is -- create level and load level `a_level' do make -- load level current_level := a_level load_level ("./levels/" + current_level + ".lvl") end make_from_first_level is -- create level and load first level do make -- load level current_level := levels.first load_level ("./levels/" + current_level + ".lvl") end make_from_savegame is -- create level and load savegame local file: PLAIN_TEXT_FILE do make create file.make ("./save.sav") if file.exists then initialize_level load_savegame else make_from_first_level end end initialize_level is -- initialize level elements local i: INTEGER image_path: STRING a_bar: BB_BAR do make_scene -- initialize collision detector -- 4 collision groups are needed, blocks, items, balls, bar create collision_detector.make (4) collision_detector.set_search_depth (4) collision_detector.set_movement_check (0) -- load images and fonts bitmap_factory.create_bitmap_from_image ("./images/arial256.gif") create score_font.make (bitmap_factory.last_bitmap) bitmap_factory.create_bitmap_from_image ("./images/ball.gif") life_image := bitmap_factory.last_bitmap create block_images.make (-1,9) from i := -1 until i > 9 loop if i /= 0 then image_path := "./images/block" image_path.append_integer (i) image_path.append (".gif") bitmap_factory.create_bitmap_from_image (image_path) block_images.put (bitmap_factory.last_bitmap, i) end i := i + 1 end -- create items create items.make create item_images.make (1,Number_of_items) from i := 1 until i > Number_of_items loop bitmap_factory.create_bitmap_from_image ("./images/items/" + i.out + ".png") item_images.put (bitmap_factory.last_bitmap, i) i := i + 1 end -- create bar create bars.make create a_bar.make (Window_width / 2, Window_height - 30.0, Current) -- create block lists create blocks.make create blocks_to_destroy.make create block_field.make (number_of_rows, number_of_columns) remaining_blocks := 0 -- create balls create balls.make -- initialize keyboard create pressed_keys.make (1, Number_of_keyboard_keys) from i := 1 until i > Number_of_keyboard_keys loop pressed_keys.put (False, i) i := i + 1 end -- create other lists and objects create random.make create item_display_effects.make end initialize_scene is -- initialize scene local a_ball: BB_BALL do -- create bar --create a_bar.make (Window_width / 2, Window_height - 30.0, Current) create a_ball.make_on_bar (bars.first) last_time := time.ticks frame_delay := 0 set_frame_counter_visibility (True) score_multiplier := 1 number_of_active_items := 0 is_paused := False inspect difficulty when Difficulty_foolproof then score_for_extra_life := score_for_extra_life_foolproof remaining_blocks_for_next_level := Remaining_blocks_for_next_level_foolproof when Difficulty_easy then score_for_extra_life := score_for_extra_life_easy remaining_blocks_for_next_level := Remaining_blocks_for_next_level_easy when Difficulty_normal then score_for_extra_life := score_for_extra_life_normal remaining_blocks_for_next_level := Remaining_blocks_for_next_level_normal when Difficulty_hard then score_for_extra_life := score_for_extra_life_hard remaining_blocks_for_next_level := Remaining_blocks_for_next_level_hard when Difficulty_insane then score_for_extra_life := score_for_extra_life_insane remaining_blocks_for_next_level := Remaining_blocks_for_next_level_insane else score_for_extra_life := score_for_extra_life_normal remaining_blocks_for_next_level := Remaining_blocks_for_next_level_normal end are_balls_invulnerable := False can_balls_fly_away := False are_balls_destroying_everything := False are_grey_blocks_blended_out := False is_bad_item_in_effect := False is_frame_counter_visibility_set := False random.set_seed (time.ticks) end uninitialize_scene is -- cleanup do time.quit end read_level_list is -- read level list local file: PLAIN_TEXT_FILE do create levels.make levels.set_equality_tester (create {UC_STRING_EQUALITY_TESTER}) create file.make ("./levels/" + level_set + ".ord") if file.exists then create file.make_open_read ("./levels/" + level_set + ".ord") else create file.make_open_read ("./levels/custom.ord") level_set := "custom" end from file.start until file.end_of_file loop file.read_line levels.force_last (file.last_string.substring (1, file.last_string.count - 4)) end if not is_checksum_correct (level_set + ".ord") then start_cheater_scene end end feature -- Access collision_detector: EM_COLLISION_DETECTOR [BB_OBJECT] -- collision detector random: RANDOM -- random number generator is used wherever it's possible and a random number is needed -- global random number because of identical random number list if several random number generators -- are created in a very short time (because they are usually initialized by the time) find_block_by_position (a_row, a_column: INTEGER): BB_BLOCK is -- block at position (a_row, a_column), Void if there is no such block do Result := Void from blocks.start until Result /= Void or blocks.after loop if blocks.item_for_iteration.row = a_row and blocks.item_for_iteration.column = a_column then Result := blocks.item_for_iteration end blocks.forth end end feature -- Status report score: INTEGER -- current score lifes: INTEGER -- remaining lifes remaining_blocks: INTEGER -- number of remaining destructible blocks score_multiplier: DOUBLE -- factor with which score gain is multiplied are_balls_invulnerable: BOOLEAN -- are the balls invulnerable? (bounce off from the bottom) can_balls_fly_away: BOOLEAN -- can balls fly away? (get destroyed at the top, like at the bottom normally) are_balls_destroying_everything: BOOLEAN -- are balls destroying everything and fly through blocks? are_grey_blocks_blended_out: BOOLEAN -- are grey blocks blended out so balls can fly through them? is_bad_item_in_effect: BOOLEAN -- is "the" bad item in effect? difficulty: INTEGER -- difficulty (see BB_CONSTANTS) is_paused: BOOLEAN -- is game paused? feature -- Status setting set_score (an_integer: INTEGER) is -- set `score' do score := an_integer from until score < last_score_extra_life + Score_for_extra_life loop last_score_extra_life := last_score_extra_life + Score_for_extra_life gain_life end ensure score_set: score = an_integer end set_remaining_blocks (an_integer: INTEGER) is -- set `remaining_blocks' do remaining_blocks := an_integer ensure remaining_blocks_set: remaining_blocks = an_integer end set_score_multiplier (a_double: DOUBLE) is -- set `score_multiplier' do score_multiplier := a_double ensure score_multiplier_set: score_multiplier = a_double end set_everything_to_normal is -- set all balls and blocks an the bar to normal local ball_cursor: DS_LINKED_LIST_CURSOR [BB_BALL] i: INTEGER do -- remove all item effect from i := 1 until i > Number_of_items loop time.remove_timed_procedure (agent stop_item_effect (i)) i := i + 1 end number_of_active_items := 0 -- reset the level stop_sound (Sound_invulnerable) are_balls_invulnerable := False can_balls_fly_away := False are_balls_destroying_everything := False if are_grey_blocks_blended_out then stop_item_effect (Item_type_disappear) end are_grey_blocks_blended_out := False is_bad_item_in_effect := False -- reset the bar bars.first.set_normal_size bars.first.set_normal_steering bars.first.set_normal_speed bars.first.set_opaque bars.first.set_sticky (False) bars.first.set_inverted (False) bars.first.set_frozen (False) bars.first.set_no_driving -- reset the balls create ball_cursor.make (balls) from ball_cursor.start until ball_cursor.after loop ball_cursor.item.set_opaque ball_cursor.item.set_normal_speed ball_cursor.forth end set_score_multiplier (1.0) end toggle_paused is -- toggle `is_paused' do is_paused := not is_paused time.set_are_timed_procedures_paused (is_paused) ensure paused_toggled: is_paused = not old is_paused end set_password (a_string: STRING) is -- set `password', Void if it should not be drawn do if a_string = Void then password := Void else password := a_string.out end end feature -- Commands gain_life is -- gain a life do lifes := lifes + 1 if lifes > 99 then lifes := 99 end play_sound (Sound_life_up) ensure life_gained: old lifes < 99 implies lifes = old lifes + 1 max_99_lifes: lifes <= 99 end subtract_life is -- loose a life require lifes_not_negative: lifes >= 0 do lifes := lifes - 1 play_sound (Sound_loose_life) -- game over if lifes < 0 then game_over end end goto_next_level is -- goto next level local a_ball: BB_BALL file: PLAIN_TEXT_FILE do -- set random seed to randomize it better random.set_seed (time.ticks) clear_level set_everything_to_normal -- load new level levels.start levels.search_forth (current_level) levels.forth if levels.after then create file.make ("./save.sav") if file.exists then file.delete end create file.make ("./save.chk") if file.exists then file.delete end -- create a new ball to avoid a life loss until next scene is really started create a_ball.make_on_bar (bars.first) set_next_scene (create {BB_GAME_FINISHED}.make (score + lifes * Bonus_score_for_remaining_life, level_set)) start_next_scene else current_level := levels.item_for_iteration load_level ("./levels/" + current_level + ".lvl") write_savegame -- create a new ball create a_ball.make_on_bar (bars.first) end end game_over is -- show game over screen local file: PLAIN_TEXT_FILE do create file.make ("./save.sav") if file.exists then file.delete end create file.make ("./save.chk") if file.exists then file.delete end set_next_scene (create {BB_GAME_OVER}.make (score, level_set)) start_next_scene end back_to_menu is -- go back to menu do stop_sound (Sound_invulnerable) play_music ("./bgm/menu.ogg") set_next_scene (create {BB_SINGLEPLAYER}.make) start_next_scene end start_cheater_scene is -- start cheater scene do stop_sound (Sound_invulnerable) play_music ("./bgm/menu.ogg") set_next_scene (create {BB_CHEATER}.make) start_next_scene end feature -- Miscellaneous run (a_screen: EM_VIDEO_SURFACE) is -- run scene and show it on `a_screen'. do Precursor {EM_SCENE} (a_screen) end move_all is -- move all balls, bars and items local ball_cursor: DS_LINKED_LIST_CURSOR [BB_BALL] item_cursor: DS_LINKED_LIST_CURSOR [BB_ITEM] do create ball_cursor.make (balls) from ball_cursor.start until ball_cursor.after loop ball_cursor.item.move (frame_delay) ball_cursor.forth end create item_cursor.make (items) from item_cursor.start until item_cursor.after loop item_cursor.item.move (frame_delay) item_cursor.forth end if bb_settings.device.is_equal ("Joystick") then if joystick_position > 5000 then bars.first.move_right ( joystick_position / 32767) elseif joystick_position < -5000 then bars.first.move_left (joystick_position / -32768) else bars.first.move_not end else if pressed_keys.item (BB_keyboard_key_right) then bars.first.move_right (1.0) end if pressed_keys.item (BB_keyboard_key_left) then bars.first.move_left (1.0) end if not pressed_keys.item (BB_keyboard_key_left) and not pressed_keys.item (BB_keyboard_key_right) then bars.first.move_not end end end destroy_blocks is -- destroys all blocks in `blocks_to_destroy' local block_cursor: DS_LINKED_LIST_CURSOR [BB_BLOCK] do create block_cursor.make (blocks_to_destroy) from block_cursor.start until block_cursor.after loop block_cursor.item.destroy block_cursor.forth end blocks_to_destroy.wipe_out ensure blocks_to_destroy_is_empty: blocks_to_destroy.is_empty end set_ball_from_left_out_of_block (ball: BB_BALL; block: BB_BLOCK) is -- set `ball' which hits `block' from left out of `block' in direction -`ball.movement' local delta_x: DOUBLE do if ball.movement.x > 0 and ball.border_right >= block.border_left then delta_x := ball.border_right - block.border_left + 0.0001 ball.center.set_x (ball.center.x - delta_x) ball.center.set_y (ball.center.y - delta_x * ball.movement.y / ball.movement.x) end end set_ball_from_right_out_of_block (ball: BB_BALL; block: BB_BLOCK) is -- set `ball' which hits `block' from left out of `block' in direction -`ball.movement' local delta_x: DOUBLE do if ball.movement.x < 0 and block.border_right >= ball.border_left then delta_x := block.border_right - ball.border_left + 0.0001 ball.center.set_x (ball.center.x + delta_x) ball.center.set_y (ball.center.y - delta_x * ball.movement.y / ball.movement.x) end end create_simple_level (number_of_block_hits: INTEGER) is -- creates a simple level, each block has to be hit `number_of_block_hits' times local i, j: INTEGER a_block: BB_BLOCK a_ball: BB_BALL do if number_of_block_hits \\ 2 = 1 then bitmap_factory.create_bitmap_from_image ("./images/backgrounds/fina_silvite_800.jpg") else bitmap_factory.create_bitmap_from_image ("./images/backgrounds/slayers1.jpg") end background := bitmap_factory.last_bitmap from i := 5 until i = 15 loop from j := 3 until j > number_of_columns - 2 loop random.forth if random.item \\ 50 < number_of_block_hits then create a_block.make (i, j, 0, Current) else create a_block.make (i, j, number_of_block_hits, Current) end j := j + 1 end i := i + 1 end create a_ball.make_on_bar (bars.first) end create_collision_testing_level is -- creates a collision testing level local a_ball: BB_BALL a_block: BB_BLOCK i, row, column, hits: INTEGER do bitmap_factory.create_bitmap_from_image ("./images/backgrounds/fina_silvite_800.jpg") background := bitmap_factory.last_bitmap create a_ball.make (56.5, 100.0, Current) a_ball.set_stopped (False) a_ball.set_movement_angle (3 / 4 * Pi) create a_ball.make (160.0, 30.0, Current) a_ball.set_stopped (False) a_ball.set_movement_angle (1.0) create a_ball.make (377.0, 167.0, Current) a_ball.set_stopped (False) a_ball.set_movement_angle (Pi / 4) create a_ball.make (268.0, 380.0, Current) a_Ball.set_stopped (False) a_Ball.set_movement_angle (0.5) -- create a_block.make (2, 5, 0, Current) from i := 1 until i = 30 loop random.forth row := random.item \\ (number_of_rows - 6) + 3 + 1 random.forth column := random.item \\ (number_of_columns - 3) + 3 + 1 random.forth hits := random.item \\ 10 if not block_field.item (row, column) then create a_block.make (row, column, hits, Current) i := i + 1 end end -- create flat-corner-jump-test blocks from i := 1 until i > number_of_rows - 3 loop create a_block.make (i, 1, 0, Current) create a_block.make (i, 3, 0, Current) i := i + 1 end create a_block.make (i - 1, 2, 0, Current) from i := 4 until i > number_of_columns loop create a_block.make (1, i, 0, Current) create a_block.make (3, i, 0, Current) i := i + 1 end end start_item_effect (item_type: INTEGER) is -- start item effect of `item_type' local a_double: DOUBLE a_ball: BB_BALL ball_cursor: DS_LINKED_LIST_CURSOR [BB_BALL] block_cursor: DS_LINKED_LIST_CURSOR [BB_BLOCK] do inspect item_type when Item_type_bar_large then time.remove_timed_procedure (agent stop_item_effect (Item_type_bar_small)) if bars.first.is_small then set_score_multiplier (score_multiplier / Item_multiplier_bar_small) number_of_active_items := number_of_active_items - 1 end if not bars.first.is_large then set_score_multiplier (score_multiplier * Item_multiplier_bar_large) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end bars.first.set_large time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_bar_large) add_item_display_effect (item_type) play_sound (Sound_bar_large) when Item_type_bar_small then time.remove_timed_procedure (agent stop_item_effect (Item_type_bar_large)) if bars.first.is_large then set_score_multiplier (score_multiplier / Item_multiplier_bar_large) number_of_active_items := number_of_active_items - 1 end if not bars.first.is_small then set_score_multiplier (score_multiplier * Item_multiplier_bar_small) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end bars.first.set_small time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_bar_small) add_item_display_effect (item_type) play_sound (Sound_bar_small) when Item_type_bar_fast then time.remove_timed_procedure (agent stop_item_effect (Item_type_bar_slow)) if bars.first.is_slow then set_score_multiplier (score_multiplier / Item_multiplier_bar_slow) number_of_active_items := number_of_active_items - 1 end if not bars.first.is_fast then set_score_multiplier (score_multiplier * Item_multiplier_bar_fast) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end bars.first.set_fast time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_bar_fast) add_item_display_effect (item_type) play_sound (Sound_bar_fast) when Item_type_bar_slow then time.remove_timed_procedure (agent stop_item_effect (Item_type_bar_fast)) if bars.first.is_fast then set_score_multiplier (score_multiplier / Item_multiplier_bar_fast) number_of_active_items := number_of_active_items - 1 end if not bars.first.is_slow then set_score_multiplier (score_multiplier * Item_multiplier_bar_slow) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end bars.first.set_slow time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_bar_slow) add_item_display_effect (item_type) play_sound (Sound_bar_slow) when Item_type_lifeup then gain_life when Item_type_invert then if not bars.first.is_inverted then set_score_multiplier (score_multiplier * Item_multiplier_invert) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end bars.first.set_inverted (True) time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_invert) add_item_display_effect (item_type) play_sound (Sound_input_inverted) when Item_type_freeze then if not bars.first.is_frozen then set_score_multiplier (score_multiplier * Item_multiplier_freeze) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end bars.first.set_frozen (True) time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_freeze) add_item_display_effect (item_type) play_sound (Sound_input_freezed) when Item_type_points then a_double := 1000 * score_multiplier set_score (score + a_double.rounded) play_sound (Sound_points) when Item_type_loose_life then a_double := 3000 * score_multiplier set_score (score + a_double.rounded) subtract_life when Item_type_invulnerable then if not are_balls_invulnerable then set_score_multiplier (score_multiplier * Item_multiplier_invulnerable) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end are_balls_invulnerable := True time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_invulnerable) add_item_display_effect (item_type) stop_sound (Sound_invulnerable) pause_music play_sound (Sound_invulnerable) when Item_type_ball_fast then time.remove_timed_procedure (agent stop_item_effect (Item_type_ball_slow)) if balls.first.is_slow then set_score_multiplier (score_multiplier / Item_multiplier_ball_slow) number_of_active_items := number_of_active_items - 1 end if not balls.first.is_fast then set_score_multiplier (score_multiplier * Item_multiplier_ball_fast) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end create ball_cursor.make (balls) from ball_cursor.start until ball_cursor.after loop ball_cursor.item.set_fast ball_cursor.forth end time.add_timed_procedure (agent stop_item_effect (Item_type_ball_fast), Item_duration_ball_fast) add_item_display_effect (item_type) play_sound (Sound_ball_fast) when Item_type_ball_slow then time.remove_timed_procedure (agent stop_item_effect (Item_type_ball_fast)) if balls.first.is_fast then set_score_multiplier (score_multiplier / Item_multiplier_ball_fast) number_of_active_items := number_of_active_items - 1 end if not balls.first.is_slow then set_score_multiplier (score_multiplier * Item_multiplier_ball_slow) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end create ball_cursor.make (balls) from ball_cursor.start until ball_cursor.after loop ball_cursor.item.set_slow ball_cursor.forth end time.add_timed_procedure (agent stop_item_effect (Item_type_ball_slow), Item_duration_ball_slow) add_item_display_effect (item_type) play_sound (Sound_ball_slow) when Item_type_fly_away then if not can_balls_fly_away then set_score_multiplier (score_multiplier * Item_multiplier_fly_away) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end can_balls_fly_away := True time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_fly_away) add_item_display_effect (item_type) play_sound (Sound_fly_away) when Item_type_angle_high then time.remove_timed_procedure (agent stop_item_effect (Item_type_angle_low)) if bars.first.is_understeering then set_score_multiplier (score_multiplier / Item_multiplier_angle_low) number_of_active_items := number_of_active_items - 1 end if not bars.first.is_oversteering then set_score_multiplier (score_multiplier * Item_multiplier_angle_high) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end bars.first.set_oversteering time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_angle_high) add_item_display_effect (item_type) play_sound (Sound_angle_high) when Item_type_angle_low then time.remove_timed_procedure (agent stop_item_effect (Item_type_angle_high)) if bars.first.is_oversteering then set_score_multiplier (score_multiplier / Item_multiplier_angle_high) number_of_active_items := number_of_active_items - 1 end if not bars.first.is_understeering then set_score_multiplier (score_multiplier * Item_multiplier_angle_low) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end bars.first.set_understeering time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_angle_low) add_item_display_effect (item_type) play_sound (Sound_angle_low) when Item_type_loose_points then a_double := 1000 * score_multiplier set_score (score - a_double.rounded) play_sound (Sound_loose_points) when Item_type_split_to_2 then create a_ball.make_from_other (balls.first) balls.first.rotate_movement (Pi / 4) a_ball.rotate_movement (- Pi / 4) play_sound (Sound_split_to_2) when Item_type_split_to_4 then create a_ball.make_from_other (balls.first) a_ball.rotate_movement (Pi / 4) create a_ball.make_from_other (balls.first) a_ball.rotate_movement (Pi * 3 / 4) create a_ball.make_from_other (balls.first) a_ball.rotate_movement (- Pi * 3 / 4) balls.first.rotate_movement (- Pi / 4) play_sound (Sound_split_to_4) when Item_type_split_to_6 then create a_ball.make_from_other (balls.first) a_ball.rotate_movement (Pi / 6) create a_ball.make_from_other (balls.first) a_ball.rotate_movement (Pi * 3 / 6) create a_ball.make_from_other (balls.first) a_ball.rotate_movement (Pi * 5 / 6) create a_ball.make_from_other (balls.first) a_ball.rotate_movement (- Pi * 5 / 6) create a_ball.make_from_other (balls.first) a_ball.rotate_movement (- Pi * 3 / 6) balls.first.rotate_movement (- Pi / 6) play_sound (Sound_split_to_6) when Item_type_artillery_fire then create a_ball.make (Window_width / 9, Window_height - 2 * balls.first.half_height - 1, Current) a_ball.set_movement_angle (Pi / 2) a_ball.set_stopped (False) create a_ball.make (Window_width * 2 / 9, Window_height - 2 * balls.first.half_height - 1, Current) a_ball.set_movement_angle (Pi / 2) a_ball.set_stopped (False) create a_ball.make (Window_width * 3 / 9, Window_height - 2 * balls.first.half_height - 1, Current) a_ball.set_movement_angle (Pi / 2) a_ball.set_stopped (False) create a_ball.make (Window_width * 4 / 9, Window_height - 2 * balls.first.half_height - 1, Current) a_ball.set_movement_angle (Pi / 2) a_ball.set_stopped (False) create a_ball.make (Window_width * 5 / 9, Window_height - 2 * balls.first.half_height - 1, Current) a_ball.set_movement_angle (Pi / 2) a_ball.set_stopped (False) create a_ball.make (Window_width * 6 / 9, Window_height - 2 * balls.first.half_height - 1, Current) a_ball.set_movement_angle (Pi / 2) a_ball.set_stopped (False) create a_ball.make (Window_width * 7 / 9, Window_height - 2 * balls.first.half_height - 1, Current) a_ball.set_movement_angle (Pi / 2) a_ball.set_stopped (False) create a_ball.make (Window_width * 8 / 9, Window_height - 2 * balls.first.half_height - 1, Current) a_ball.set_movement_angle (Pi / 2) a_ball.set_stopped (False) play_sound (Sound_artillery) when Item_type_ball_transparent then if not balls.first.is_transparent then set_score_multiplier (score_multiplier * Item_multiplier_ball_transparent) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end create ball_cursor.make (balls) from ball_cursor.start until ball_cursor.after loop ball_cursor.item.set_transparent ball_cursor.forth end time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_ball_transparent) add_item_display_effect (item_type) play_sound (Sound_transparent_ball) when Item_type_next_level then play_sound (Sound_next_level) goto_next_level when Item_type_stick_on_bar then if not bars.first.is_sticky then set_score_multiplier (score_multiplier * Item_multiplier_stick_on_bar) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end bars.first.set_sticky (True) time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_stick_on_bar) add_item_display_effect (item_type) play_sound (Sound_stick_on_bar) when Item_type_destroy_all then if not are_balls_destroying_everything then set_score_multiplier (score_multiplier * Item_multiplier_destroy_all) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end are_balls_destroying_everything := True time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_destroy_all) add_item_display_effect (item_type) play_sound (Sound_destroy_all) when Item_type_destroy_random then create block_cursor.make (blocks) from block_cursor.start until block_cursor.after loop random.forth if random.item \\ 5 = 0 then block_cursor.item.set_item_type (Item_type_nothing) if not blocks_to_destroy.has (block_cursor.item) then blocks_to_destroy.force_last (block_cursor.item) end end block_cursor.forth end play_sound (Sound_destroy_random) when Item_type_disappear then if not are_grey_blocks_blended_out then set_score_multiplier (score_multiplier * Item_multiplier_disappear) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end are_grey_blocks_blended_out := True create block_cursor.make (blocks) from block_cursor.start until block_cursor.after loop if block_cursor.item.number_of_hits = -1 then block_field.put (False, block_cursor.item.row, block_cursor.item.column) block_cursor.item.set_collidable (False) end block_cursor.forth end time.add_timed_procedure (agent stop_item_effect (Item_type_disappear), Item_duration_disappear) add_item_display_effect (item_type) play_sound (Sound_disappear) when Item_type_bad_item then if not is_bad_item_in_effect then set_score_multiplier (score_multiplier * Item_multiplier_bad_item) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end is_bad_item_in_effect := True play_sound (Sound_bad_item) set_are_sounds_muted (True) start_item_effect (Item_type_fly_away) start_item_effect (Item_type_ball_fast) start_item_effect (Item_type_bar_slow) start_item_effect (Item_type_bar_small) set_are_sounds_muted (False) time.add_timed_procedure (agent stop_item_effect (Item_type_bad_item), Item_duration_bad_item) add_item_display_effect (item_type) when Item_type_good_item then play_sound (Sound_good_item) start_item_effect (Item_type_invulnerable) set_are_sounds_muted (True) start_item_effect (Item_type_ball_slow) start_item_effect (Item_type_bar_large) start_item_effect (Item_type_bar_fast) set_are_sounds_muted (False) when Item_type_russian_roulette then random.forth inspect random.item \\ 8 when 0 then start_item_effect (Item_type_lifeup) when 1 then start_item_effect (Item_type_loose_life) when 2 then start_item_effect (Item_type_good_item) when 3 then start_item_effect (Item_type_bad_item) when 4 then start_item_effect (Item_type_destroy_random) when 5 then start_item_effect (Item_type_freeze) when 6 then start_item_effect (Item_type_next_level) when 7 then start_item_effect (Item_type_fly_away) end when Item_type_bar_transparent then if not bars.first.is_transparent then set_score_multiplier (score_multiplier * Item_multiplier_bar_transparent) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_bar_transparent) add_item_display_effect (item_type) bars.first.set_transparent play_sound (Sound_transparent_bar) when Item_type_ball_random_direction then from balls.start until balls.after loop random.forth a_double := (random.item \\ 1000) / 1000 * (Pi - 2 * Minimum_angle) + Minimum_angle random.forth if random.item \\ 2 = 0 then a_double := a_double + Pi end balls.item_for_iteration.set_movement_angle (a_double) balls.forth end set_score (score + (200 * score_multiplier).rounded) play_sound (Sound_divert) when Item_type_bar_drive_left then time.remove_timed_procedure (agent stop_item_effect (item_type_bar_drive_right)) if bars.first.is_driving_right then set_score_multiplier (score_multiplier / Item_multiplier_bar_drive_right) number_of_active_items := number_of_active_items - 1 end if not bars.first.is_driving_left then set_score_multiplier (score_multiplier * Item_multiplier_bar_drive_left) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_bar_drive_left) add_item_display_effect (item_type) bars.first.set_drive_left play_sound (Sound_drive_left) when Item_type_bar_drive_right then time.remove_timed_procedure (agent stop_item_effect (item_type_bar_drive_left)) if bars.first.is_driving_left then set_score_multiplier (score_multiplier / Item_multiplier_bar_drive_left) number_of_active_items := number_of_active_items - 1 end if not bars.first.is_driving_right then set_score_multiplier (score_multiplier * Item_multiplier_bar_drive_right) number_of_active_items := number_of_active_items + 1 else time.remove_timed_procedure (agent stop_item_effect (item_type)) end time.add_timed_procedure (agent stop_item_effect (item_type), Item_duration_bar_drive_right) add_item_display_effect (item_type) bars.first.set_drive_right play_sound (Sound_drive_right) end end stop_item_effect (item_type: INTEGER) is -- stop item effect of `item_type' local ball_cursor: DS_LINKED_LIST_CURSOR [BB_BALL] block_cursor: DS_LINKED_LIST_CURSOR [BB_BLOCK] do inspect item_type when Item_type_bar_large then bars.first.set_normal_size set_score_multiplier (score_multiplier / Item_multiplier_bar_large) number_of_active_items := number_of_active_items - 1 play_sound (Sound_bar_small) when Item_type_bar_small then bars.first.set_normal_size set_score_multiplier (score_multiplier / Item_multiplier_bar_small) number_of_active_items := number_of_active_items - 1 play_sound (Sound_bar_large) when Item_type_bar_fast then bars.first.set_normal_speed set_score_multiplier (score_multiplier / Item_multiplier_bar_fast) number_of_active_items := number_of_active_items - 1 play_sound (Sound_bar_slow) when Item_type_bar_slow then bars.first.set_normal_speed set_score_multiplier (score_multiplier / Item_multiplier_bar_slow) number_of_active_items := number_of_active_items - 1 play_sound (Sound_bar_fast) when Item_type_lifeup then -- has no duration when Item_type_invert then bars.first.set_inverted (False) set_score_multiplier (score_multiplier / Item_multiplier_invert) number_of_active_items := number_of_active_items - 1 when Item_type_freeze then bars.first.set_frozen (False) set_score_multiplier (score_multiplier / Item_multiplier_freeze) number_of_active_items := number_of_active_items - 1 play_sound (Sound_input_freezed_off) when Item_type_points then -- has no duration when Item_type_loose_life then -- has no duration when Item_type_invulnerable then are_balls_invulnerable := False set_score_multiplier (score_multiplier / Item_multiplier_invulnerable) number_of_active_items := number_of_active_items - 1 resume_music when Item_type_ball_fast then create ball_cursor.make (balls) from ball_cursor.start until ball_cursor.after loop ball_cursor.item.set_normal_speed ball_cursor.forth end set_score_multiplier (score_multiplier / Item_multiplier_ball_fast) number_of_active_items := number_of_active_items - 1 play_sound (Sound_ball_slow) when Item_type_ball_slow then create ball_cursor.make (balls) from ball_cursor.start until ball_cursor.after loop ball_cursor.item.set_normal_speed ball_cursor.forth end set_score_multiplier (score_multiplier / Item_multiplier_ball_slow) number_of_active_items := number_of_active_items - 1 play_sound (Sound_ball_fast) when Item_type_fly_away then can_balls_fly_away := False set_score_multiplier (score_multiplier / Item_multiplier_fly_away) number_of_active_items := number_of_active_items - 1 play_sound (Sound_fly_away_off) when Item_type_angle_high then bars.first.set_normal_steering set_score_multiplier (score_multiplier / Item_multiplier_angle_high) number_of_active_items := number_of_active_items - 1 play_sound (Sound_angle_low) when Item_type_angle_low then bars.first.set_normal_steering set_score_multiplier (score_multiplier / Item_multiplier_angle_low) number_of_active_items := number_of_active_items - 1 play_sound (Sound_angle_high) when Item_type_loose_points then -- has no duration when Item_type_split_to_2 then -- has no duration when Item_type_split_to_4 then -- has no duration when Item_type_split_to_6 then -- has no duration when Item_type_artillery_fire then -- has no duration when Item_type_ball_transparent then create ball_cursor.make (balls) from ball_cursor.start until ball_cursor.after loop ball_cursor.item.set_opaque ball_cursor.forth end set_score_multiplier (score_multiplier / Item_multiplier_ball_transparent) number_of_active_items := number_of_active_items - 1 play_sound (Sound_transparent_ball_off) when Item_type_next_level then -- has no duration when Item_type_stick_on_bar then bars.first.set_sticky (False) set_score_multiplier (score_multiplier / Item_multiplier_stick_on_bar) number_of_active_items := number_of_active_items - 1 play_sound (Sound_stick_on_bar_off) when Item_type_destroy_all then are_balls_destroying_everything := False set_score_multiplier (score_multiplier / Item_multiplier_destroy_all) number_of_active_items := number_of_active_items - 1 play_sound (Sound_destroy_all_off) when Item_type_destroy_random then -- has no duration when Item_type_disappear then are_grey_blocks_blended_out := False create block_cursor.make (blocks) from block_cursor.start until block_cursor.after loop if block_cursor.item.number_of_hits = -1 then block_field.put (True, block_cursor.item.row, block_cursor.item.column) block_cursor.item.set_collidable (True) end block_cursor.forth end set_score_multiplier (score_multiplier / Item_multiplier_disappear) number_of_active_items := number_of_active_items - 1 play_sound (Sound_disappear) when Item_type_bad_item then is_bad_item_in_effect := False set_score_multiplier (score_multiplier / Item_multiplier_bad_item) number_of_active_items := number_of_active_items - 1 when Item_type_good_item then when Item_type_russian_roulette then when Item_type_bar_transparent then bars.first.set_opaque set_score_multiplier (score_multiplier / Item_multiplier_bar_transparent) number_of_active_items := number_of_active_items - 1 play_sound (Sound_transparent_bar_off) when Item_type_bar_drive_left then bars.first.set_no_driving set_score_multiplier (score_multiplier / Item_multiplier_bar_drive_left) number_of_active_items := number_of_active_items - 1 play_sound (Sound_drive_right) when Item_type_bar_drive_right then bars.first.set_no_driving set_score_multiplier (score_multiplier / Item_multiplier_bar_drive_right) number_of_active_items := number_of_active_items - 1 play_sound (Sound_drive_left) else end end all_balls_lost is -- reset all item effects and subtract a life and create a new ball local a_ball: BB_BALL item_cursor: DS_LINKED_LIST_CURSOR [BB_ITEM] do -- set random seed to randomize it better random.set_seed (time.ticks) create item_cursor.make (items) from item_cursor.start until item_cursor.after loop collision_detector.remove (item_cursor.item) item_cursor.forth item_cursor.remove_left end set_everything_to_normal subtract_life write_savegame create a_ball.make_on_bar (bars.first) end load_level (level_file_path: STRING) is -- load level from `level_file_path' require level_file_path_not_void: level_file_path /= Void local file: RAW_FILE level_file: PLAIN_TEXT_FILE line: STRING line_number: INTEGER a_block: BB_BLOCK do create file.make (level_file_path) if file.exists then if is_checksum_correct (level_file_path.split ('/').last) then set_remaining_blocks (0) create block_field.make (Number_of_rows, number_of_columns) create level_file.make_open_read (level_file_path) from level_file.start line_number := 1 until level_file.end_of_file loop level_file.read_line line := level_file.last_string if not line.substring (1, 2).is_equal ("//") and not line.is_equal ("") then inspect line_number when 1 then -- password set_password (line) when 2 then -- background image create file.make ("./images/backgrounds/" + line) if file.exists then bitmap_factory.create_bitmap_from_image ("./images/backgrounds/" + line) else bitmap_factory.create_bitmap_from_image ("./images/backgrounds/background.jpg") end background := bitmap_factory.last_bitmap when 3 then -- background music create file.make ("./bgm/" + line) if file.exists then play_music ("./bgm/" + line) else play_music ("./bgm/background.ogg") end else -- blocks create a_block.make_from_string (line, Current) end line_number := line_number + 1 end end level_file.close else start_cheater_scene end else back_to_menu end end load_savegame is -- load savegame and delete savegame file -- if there is no savegame file, nothing will be done local file: PLAIN_TEXT_FILE raw_file: RAW_FILE a_block: BB_BLOCK line_number: INTEGER checksum_file: PLAIN_TEXT_FILE checksum_generator: EM_CHECKSUM_GENERATOR do create file.make ("./save.sav") create checksum_file.make ("./save.chk") if file.exists and checksum_file.exists then file.make_open_read ("./save.sav") -- check the checksum checksum_file.make_open_read ("./save.chk") create checksum_generator checksum_generator.reset file.start file.read_stream (file.count) checksum_generator.append_string (file.last_string) checksum_generator.append_string (file.change_date.out) checksum_generator.generate_checksum checksum_file.read_integer if checksum_generator.checksum = checksum_file.last_integer then -- load the level file.start -- level set file.read_line level_set := file.last_string.out read_level_list -- level file.read_line current_level := file.last_string.out -- score of last bonus life file.read_line last_score_extra_life := file.last_string.to_integer -- score for an exra life file.read_line score_for_extra_life := file.last_string.to_integer -- lifes file.read_line lifes := file.last_string.to_integer -- difficulty file.read_line difficulty := file.last_string.to_integer -- score file.read_line set_score (file.last_string.to_integer) -- blocks from file.read_line until file.end_of_file loop create a_block.make_from_string (file.last_string, Current) file.read_line end file.close file.delete checksum_file.close checksum_file.delete -- load background image and background music file.make_open_read ("./levels/" + current_level + ".lvl") from file.start line_number := 1 until line_number > 3 loop file.read_line if not file.last_string.substring (1, 2).is_equal ("//") and not file.last_string.is_equal ("") then inspect line_number when 1 then -- ignore password when 2 then -- background image create raw_file.make ("./images/backgrounds/" + file.last_string) if raw_file.exists then bitmap_factory.create_bitmap_from_image ("./images/backgrounds/" + file.last_string) else bitmap_factory.create_bitmap_from_image ("./images/backgrounds/background.jpg") end background := bitmap_factory.last_bitmap when 3 then -- background music create raw_file.make ("./bgm/" + file.last_string) if raw_file.exists then play_music ("./bgm/" + file.last_string) else play_music ("./bgm/background.ogg") end else -- do nothing end line_number := line_number + 1 end end file.close -- set difficulty in settings inspect difficulty when difficulty_foolproof then bb_settings.set_difficulty ("Foolproof") when difficulty_easy then bb_settings.set_difficulty ("Easy") when difficulty_normal then bb_settings.set_difficulty ("Normal") when difficulty_hard then bb_settings.set_difficulty ("Hard") when difficulty_insane then bb_settings.set_difficulty ("Insane") else bb_settings.set_difficulty ("Normal") end bb_settings.write_user_settings -- checksum not matched else file.close file.delete checksum_file.close checksum_file.delete start_cheater_scene end end end write_savegame is -- save current game local file: PLAIN_TEXT_FILE checksum_generator: EM_CHECKSUM_GENERATOR do create file.make_open_write ("./save.sav") file.start -- level set file.put_string (level_set) file.put_new_line -- level file.put_string (current_level) file.put_new_line -- score of last bonus life file.put_integer (last_score_extra_life) file.put_new_line -- score for an extra life file.put_integer (score_for_extra_life) file.put_new_line -- lifes file.put_integer (lifes) file.put_new_line -- difficulty file.put_integer (difficulty) file.put_new_line -- score file.put_integer (score) file.put_new_line -- blocks from blocks.start until blocks.after loop file.put_string (blocks.item_for_iteration.out) file.put_new_line blocks.forth end file.close -- generate checksum create checksum_generator checksum_generator.reset -- reopen the savegamefile, only for reading -- checksum is saved in a seperate file so that the change date of the same gamefile can be included create file.make_open_read ("./save.sav") file.start file.read_stream (file.count) checksum_generator.append_string (file.last_string) checksum_generator.append_string (file.change_date.out) file.close -- open checksum file and write it create file.make_open_write ("./save.chk") checksum_generator.generate_checksum file.put_integer (checksum_generator.checksum) file.close end clear_level is -- remove all block, balls and items local ball_cursor: DS_LINKED_LIST_CURSOR [BB_BALL] item_cursor: DS_LINKED_LIST_CURSOR [BB_ITEM] do -- clear old level from blocks.start until blocks.after loop collision_detector.remove (blocks.item_for_iteration) block_field.put (False, blocks.item_for_iteration.row, blocks.item_for_iteration.column) blocks.remove_at end create ball_cursor.make (balls) from ball_cursor.start until ball_cursor.after loop collision_detector.remove (ball_cursor.item) ball_cursor.forth ball_cursor.remove_left end bars.first.balls_on_bar.wipe_out create item_cursor.make (items) from item_cursor.start until item_cursor.after loop collision_detector.remove (item_cursor.item) item_cursor.forth item_cursor.remove_left end end feature -- Collision handle_collisions is -- handle all collisions between objects local composition_cursor: DS_LINKED_LIST_CURSOR [EM_COLLISION_COMPOSITION [BB_OBJECT]] collision_cursor: DS_LINKED_LIST_CURSOR [EM_COLLISION [BB_OBJECT]] do -- check for collisions collision_detector.check_for_collision find_very_first_ball_collisions create composition_cursor.make (collision_detector.last_collisions) -- loop through all collision compositions from composition_cursor.start until composition_cursor.after loop create collision_cursor.make (composition_cursor.item) -- loop through single collisions from collision_cursor.start until collision_cursor.after loop if is_ball_block_collision (collision_cursor.item) then handle_ball_block_collision (collision_cursor.item) elseif is_ball_bar_collision (collision_cursor.item) then handle_ball_bar_collision (collision_cursor.item) elseif is_item_bar_collision (collision_cursor.item) then handle_item_bar_collision (collision_cursor.item) end collision_cursor.forth end composition_cursor.forth end end handle_collision_with_wall is -- check collisions with walls local item_cursor: DS_LINKED_LIST_CURSOR [BB_ITEM] ball_cursor: DS_LINKED_LIST_CURSOR [BB_BALL] is_ball_to_remove: BOOLEAN do create item_cursor.make (items) from item_cursor.start until item_cursor.after loop if item_cursor.item.border_top > Window_height then collision_detector.remove (item_cursor.item) item_cursor.remove else item_cursor.forth end end create ball_cursor.make (balls) is_ball_to_remove := False from ball_cursor.start until ball_cursor.after loop -- bottom if ball_cursor.item.border_bottom > Window_height then if are_balls_invulnerable then ball_cursor.item.movement.set_y (-ball_cursor.item.movement.y.abs) ball_cursor.item.center.set_y (Window_height - ball_cursor.item.half_height) play_sound (Sound_border) else collision_detector.remove (ball_cursor.item) is_ball_to_remove := True end end -- top if ball_cursor.item.border_top < 0.0 then if not can_balls_fly_away or are_balls_invulnerable then ball_cursor.item.movement.set_y (ball_cursor.item.movement.y.abs) ball_cursor.item.center.set_y (ball_cursor.item.half_height) play_sound (Sound_border) else collision_detector.remove (ball_cursor.item) is_ball_to_remove := True end end -- right if ball_cursor.item.border_right > Window_width then ball_cursor.item.movement.set_x (-ball_cursor.item.movement.x.abs) ball_cursor.item.center.set_x (Window_width - ball_cursor.item.half_width) play_sound (Sound_border) end -- left if ball_cursor.item.border_left < 0.0 then ball_cursor.item.movement.set_x (ball_cursor.item.movement.x.abs) ball_cursor.item.center.set_x (ball_cursor.item.half_width) play_sound (Sound_border) end ball_cursor.forth if is_ball_to_remove then ball_cursor.remove_left is_ball_to_remove := False end end end handle_ball_block_collision (collision: EM_COLLISION [BB_OBJECT]) is -- handles a collision between a ball and a block require is_ball_block_collision: is_ball_block_collision (collision) local ball: BB_BALL block: BB_BLOCK a_double: DOUBLE cancel_hit: BOOLEAN do if collision.collidables.first.object_type = Object_type_block then block ?= collision.collidables.first ball ?= collision.collidables.second else ball ?= collision.collidables.first block ?= collision.collidables.second end if are_balls_destroying_everything then if not blocks_to_destroy.has (block) then blocks_to_destroy.force_last (block) end a_double := 25 * score_multiplier set_score (score + a_double.rounded) elseif is_real_collision (ball, block) then cancel_hit := False -- top left corner if ball.border_top <= block.border_top and ball.border_left <= block.border_left then if block.has_upper_neighbour and block.has_left_neighbour then cancel_hit := True else -- it's a top hit if ball.border_bottom.rounded - block.border_top.rounded <= ball.border_right.rounded - block.border_left.rounded and not block.has_upper_neighbour then -- comes ball from bottom left, avoid grazing shot if ball.movement.x > 0 and ball.movement.y < 0 and not block.has_left_neighbour then ball.movement.set_x (-ball.movement.x.abs) ball.center.set_x (block.border_left - ball.half_width - 0.0001) else ball.movement.set_y (-ball.movement.y.abs) ball.center.set_y (block.border_top - ball.half_height - 0.0001) end end -- it's a left hit if ball.border_bottom.rounded - block.border_top.rounded >= ball.border_right.rounded - block.border_left.rounded and not block.has_left_neighbour then -- comes ball from top right, avoid grazing shot if ball.movement.x < 0 and ball.movement.y > 0 and not block.has_upper_neighbour then ball.movement.set_y (-ball.movement.y.abs) ball.center.set_y (block.border_top - ball.half_height - 0.0001) else ball.movement.set_x (-ball.movement.x.abs) ball.center.set_x (block.border_left - ball.half_width - 0.0001) end end end -- top right corner elseif ball.border_top <= block.border_top and ball.border_right >= block.border_right then if block.has_upper_neighbour and block.has_right_neighbour then cancel_hit := True else -- it's a top hit if ball.border_bottom.rounded - block.border_top.rounded <= block.border_right.rounded - ball.border_left.rounded and not block.has_upper_neighbour then -- comes ball from bottom right, avoid grazing shot if ball.movement.x < 0 and ball.movement.y < 0 and not block.has_right_neighbour then ball.movement.set_x (ball.movement.x.abs) ball.center.set_x (block.border_right + ball.half_width + 0.0001) else ball.movement.set_y (-ball.movement.y.abs) ball.center.set_y (block.border_top - ball.half_height - 0.0001) end end -- it's a right hit if ball.border_bottom.rounded - block.border_top.rounded >= block.border_right.rounded - ball.border_left.rounded and not block.has_right_neighbour then -- comes ball from top left, avoid grazing shot if ball.movement.x > 0 and ball.movement.y > 0 and not block.has_upper_neighbour then ball.movement.set_y (-ball.movement.y.abs) ball.center.set_y (block.border_top - ball.half_height - 0.0001) else ball.movement.set_x (ball.movement.x.abs) ball.center.set_x (block.border_right + ball.half_width + 0.0001) end end end -- bottom right elseif ball.border_right >= block.border_right and ball.border_bottom >= block.border_bottom then if block.has_lower_neighbour and block.has_right_neighbour then cancel_hit := True else -- bottom hit if block.border_bottom.rounded - ball.border_top.rounded <= block.border_right.rounded - ball.border_left.rounded and not block.has_lower_neighbour then -- comes ball from top right, avoid grazing shot if ball.movement.x < 0 and ball.movement.y > 0 and not block.has_right_neighbour then ball.movement.set_x (ball.movement.x.abs) ball.center.set_x (block.border_right + ball.half_width + 0.0001) else ball.movement.set_y (ball.movement.y.abs) ball.center.set_y (block.border_bottom + ball.half_height + 0.0001) end end -- right hit if block.border_bottom.rounded - ball.border_top.rounded >= block.border_right.rounded - ball.border_left.rounded and not block.has_right_neighbour then -- comes ball from bottom left, avoid grazing shot if ball.movement.x > 0 and ball.movement.y < 0 and not block.has_lower_neighbour then ball.movement.set_y (ball.movement.y.abs) ball.center.set_y (block.border_bottom + ball.half_height + 0.0001) else ball.movement.set_x (ball.movement.x.abs) ball.center.set_x (block.border_right + ball.half_width + 0.0001) end end end -- bottom left hit elseif ball.border_left <= block.border_left and ball.border_bottom >= block.border_bottom then if block.has_upper_neighbour and block.has_left_neighbour then cancel_hit := True else -- bottom hit if block.border_bottom.rounded - ball.border_top.rounded <= ball.border_right.rounded - block.border_left.rounded and not block.has_lower_neighbour then -- comes ball from top left, avoid grazing shot if ball.movement.x > 0 and ball.movement.y > 0 and not block.has_left_neighbour then ball.movement.set_x (-ball.movement.x.abs) ball.center.set_x (block.border_left - ball.half_width - 0.0001) else ball.movement.set_y (ball.movement.y.abs) ball.center.set_y (block.border_bottom + ball.half_height + 0.0001) end end -- left hit if block.border_bottom.rounded - ball.border_top.rounded >= ball.border_right.rounded - block.border_left.rounded and not block.has_left_neighbour then -- comes ball from bottom right, avoid grazing shot if ball.movement.x < 0 and ball.movement.y < 0 and not block.has_lower_neighbour then ball.movement.set_y (ball.movement.y.abs) ball.center.set_y (block.border_bottom + ball.half_height + 0.0001) else ball.movement.set_x (-ball.movement.x.abs) ball.center.set_x (block.border_left - ball.half_width - 0.0001) end end end -- left edge hit elseif ball.border_left <= block.border_left then ball.movement.set_x (-ball.movement.x.abs) ball.center.set_x (block.border_left - ball.half_width - 0.0001) -- right edge hit elseif ball.border_right >= block.border_right then ball.movement.set_x (ball.movement.x.abs) ball.center.set_x (block.border_right + ball.half_width + 0.0001) -- top edge hit elseif ball.border_top <= block.border_top then ball.movement.set_y (-ball.movement.y.abs) ball.center.set_y (block.border_top - ball.half_height - 0.0001) -- bottom edge hit elseif ball.border_bottom >= block.border_bottom then ball.movement.set_y (ball.movement.y.abs) ball.center.set_y (block.border_bottom + ball.half_height + 0.0001) -- `ball' is compeletele inside of `block' -- TODO: this is not true, check what the "else" really is or why balls get stuck inside a block else -- -- -- remove it and create a new ball on the bar instead -- collision_detector.remove (ball) -- balls.delete (ball) -- create ball.make_on_bar (bars.first) end if not cancel_hit then block.get_hit (ball) end end end handle_ball_bar_collision (collision: EM_COLLISION [BB_OBJECT]) is -- handle collision between ball and bar require is_ball_bar_collision: is_ball_bar_collision (collision) local ball: BB_BALL bar: BB_BAR hitting_position: DOUBLE do if collision.collidables.first.object_type = Object_type_ball then ball ?= collision.collidables.first bar ?= collision.collidables.second else ball ?= collision.collidables.second bar ?= collision.collidables.first end ball.set_number_of_successive_hits (0) if ball.movement.y > 0.0 then -- top right hit and more to the right than on the top if ball.border_top < bar.border_top and ball.border_right > bar.border_right and ball.border_bottom - bar.border_top > bar.border_right - ball.border_left then ball.movement.set_x (ball.movement.x.abs) ball.center.set_x (bar.border_right + ball.half_width + 0.0001) play_sound (Sound_border) -- top left corner and more on the left than on the top elseif ball.border_top < bar.border_top and ball.border_left < bar.border_left and ball.border_bottom - bar.border_top > ball.border_right - bar.border_left then ball.movement.set_x (- ball.movement.x.abs) ball.center.set_x (bar.border_left - ball.half_width - 0.0001) play_sound (Sound_border) else -- it's really hitting at the top and ball controlling has to be taken into account if bar.is_sticky then ball.set_stopped (True) bar.balls_on_bar.force_last (ball) play_sound (Sound_stick_on_bar_ball) elseif bar.is_understeering then ball.movement.set_y (- ball.movement.y.abs) play_sound (Sound_border) elseif bar.is_oversteering then if ball.center.x <= bar.center.x then ball.set_movement_angle (Pi - Minimum_angle) else ball.set_movement_angle (Minimum_angle) end play_sound (Sound_border) else -- calculate at which factor of the whole hitting area the ball hit (right = 0, left = 1) hitting_position := (bar.center.x - ball.center.x + bar.half_width + ball.half_width) / (bar.half_width * 2 + ball.half_width * 2) -- because of moving the bar an the ball simultaneously there can occure values below 0 and above 1 if hitting_position < 0 then hitting_position := 0 elseif hitting_position > 1 then hitting_position := 1 end ball.set_movement_angle (((Pi - 2 * Minimum_angle) * hitting_position) + Minimum_angle) play_sound (Sound_border) end ball.center.set_y (bar.border_top - ball.half_height - 0.0001) end else ball.movement.set_y (ball.movement.y.abs) ball.center.set_y (bar.border_bottom + ball.half_height + 0.0001) play_sound (Sound_border) end end handle_item_bar_collision (collision: EM_COLLISION [BB_OBJECT]) is -- start item effects require is_item_bar_collison: is_item_bar_collision (collision) local a_bar: BB_BAR an_item: BB_ITEM do if collision.collidables.first.object_type = Object_type_item then an_item ?= collision.collidables.first a_bar ?= collision.collidables.second else an_item ?= collision.collidables.second a_bar ?= collision.collidables.first end start_item_effect (an_item.item_type) -- remove item collision_detector.remove (an_item) items.delete (an_item) end is_ball_block_collision (collision: EM_COLLISION [BB_OBJECT]) : BOOLEAN is -- is `collision' a collision between a block and a ball? require collision_not_void: collision /= Void do Result := collision.collidables.first.object_type = Object_type_ball and collision.collidables.second.object_type = Object_type_block Result := Result or collision.collidables.first.object_type = Object_type_block and collision.collidables.second.object_type = Object_type_ball end is_ball_bar_collision (collision: EM_COLLISION [BB_OBJECT]) : BOOLEAN is -- is `collision' a collision between a block and a ball? require collision_not_void: collision /= Void do Result := collision.collidables.first.object_type = Object_type_ball and collision.collidables.second.object_type = Object_type_bar Result := Result or collision.collidables.first.object_type = Object_type_bar and collision.collidables.second.object_type = Object_type_ball end is_item_block_collision (collision: EM_COLLISION [BB_OBJECT]) : BOOLEAN is -- is `collision' a collision between an item and a block? require collision_not_void: collision /= Void do Result := collision.collidables.first.object_type = Object_type_item and collision.collidables.second.object_type = Object_type_block Result := Result or collision.collidables.first.object_type = Object_type_block and collision.collidables.second.object_type = Object_type_item end is_item_bar_collision (collision: EM_COLLISION [BB_OBJECT]) : BOOLEAN is -- is `collision' a collision between an item and a bar? require collision_not_void: collision /= Void do Result := collision.collidables.first.object_type = Object_type_item and collision.collidables.second.object_type = Object_type_bar Result := Result or collision.collidables.first.object_type = Object_type_bar and collision.collidables.second.object_type = Object_type_item end find_very_first_ball_collisions is -- finds very first collision points of all balls `ball' and sets their `last_center' there local composition_cursor: DS_LINKED_LIST_CURSOR [EM_COLLISION_COMPOSITION [BB_OBJECT]] collision_cursor: DS_LINKED_LIST_CURSOR [EM_COLLISION [BB_OBJECT]] ball_cursor: DS_LINKED_LIST_CURSOR [BB_BALL] a_ball: BB_BALL a_block: BB_BLOCK a_collision: EM_COLLISION [BB_OBJECT] do -- loop through all collisions, check for balls and their first collision create composition_cursor.make (collision_detector.last_collisions) from composition_cursor.start until composition_cursor.after loop create collision_cursor.make (composition_cursor.item) from collision_cursor.start until collision_cursor.after loop a_collision := collision_cursor.item -- check if a ball is involved and if it collides with a block or a bar if is_ball_block_collision (a_collision) or is_ball_bar_collision (a_collision) then -- set `a_ball's `first_collision_time' if necessary a_ball ?= collision_cursor.item.collidables.first if a_ball = Void then a_ball ?= collision_cursor.item.collidables.second end check a_ball /= Void end if a_ball.first_collision_time > collision_cursor.item.time then if is_ball_block_collision (a_collision) then a_block ?= a_collision.collidables.first if a_block = Void then a_block ?= a_collision.collidables.second end check a_block /= Void end if is_real_collision (a_ball, a_block) then a_ball.set_first_collision_time (collision_cursor.item.time) end else a_ball.set_first_collision_time (collision_cursor.item.time) end end a_ball := Void end collision_cursor.forth end composition_cursor.forth end -- set new ball positions create ball_cursor.make (balls) from ball_cursor.start until ball_cursor.after loop a_ball := ball_cursor.item a_ball.center.set_x (a_ball.center.x - (1 - a_ball.first_collision_time) * a_ball.movement.x / 1000 * frame_delay) a_ball.center.set_y (a_ball.center.y - (1 - a_ball.first_collision_time) * a_ball.movement.y / 1000 * frame_delay) -- a_ball.move_by (- (1 - a_ball.first_collision_time) * a_ball.movement.x / 1000 * frame_delay, - (1 - a_ball.first_collision_time) * a_ball.movement.y / 1000 * frame_delay) a_ball.last_center.set_x (a_ball.center.x) a_ball.last_center.set_y (a_ball.center.y) ball_cursor.forth end end is_real_collision (a_ball: BB_BALL; a_block: BB_BLOCK): BOOLEAN is -- did `a_ball' really collide with `a_block' and not only because of low fps? -- check uses `a_ball.last_center' which is set by `find_very_first_collision' -- also checks whether `a_block' is collidable do Result := a_ball.last_center.y - a_ball.half_height <= a_block.border_top and a_ball.last_center.y + a_ball.half_height >= a_block.border_top Result := Result or a_ball.last_center.y - a_ball.half_height <= a_block.border_bottom and a_ball.last_center.y + a_ball.half_height >= a_block.border_bottom Result := Result or a_ball.last_center.x - a_ball.half_width <= a_block.border_left and a_ball.last_center.x + a_ball.half_width >= a_block.border_left Result := Result or a_ball.last_center.x - a_ball.half_width <= a_block.border_right and a_ball.last_center.x + a_ball.half_width >= a_block.border_right Result := Result and a_block.is_collidable end feature -- Drawing draw_level is -- draw current level local ball_cursor: DS_LINKED_LIST_CURSOR [BB_BALL] bar_cursor: DS_LINKED_LIST_CURSOR [BB_BAR] block_cursor: DS_LINKED_LIST_CURSOR [BB_BLOCK] item_cursor: DS_LINKED_LIST_CURSOR [BB_ITEM] a_rectangle: EM_RECTANGLE temp_string: STRING i: INTEGER rectangle: EM_RECTANGLE do background.draw (screen) create block_cursor.make (blocks) from block_cursor.start until block_cursor.after loop if not are_grey_blocks_blended_out or not (block_cursor.item.number_of_hits = -1) then block_cursor.item.draw (screen) end block_cursor.forth end draw_active_items create item_cursor.make (items) from item_cursor.start until item_cursor.after loop item_cursor.item.draw (screen) item_cursor.forth end create ball_cursor.make (balls) from ball_cursor.start until ball_cursor.after loop ball_cursor.item.draw (screen) ball_cursor.forth end create bar_cursor.make (bars) from bar_cursor.start until bar_cursor.after loop bar_cursor.item.draw (screen) bar_cursor.forth end if frame_counter.is_visible then create a_rectangle.make (create {EM_VECTOR_2D}.make (0.0, 0.0), create {EM_VECTOR_2D}.make (130.0, 15.0)) a_rectangle.set_fill_color (create {EM_COLOR}.make_black) a_rectangle.draw (screen) frame_counter.draw (screen) end temp_string := score.out score_font.draw_string (temp_string, screen, Window_width - score_font.string_width (temp_string), Window_height - score_font.string_height (temp_string)) -- draw lifes if lifes <= 9 then from i := 0 until i >= lifes loop screen.blit_surface (life_image, 5 + (i * (life_image.width + 5)), Window_height - life_image.height - 5) i := i + 1 end -- draw "lifes x" life_image else temp_string := lifes.out temp_string.extend ('x') score_font.draw_string (temp_string, screen, 0, Window_height - score_font.string_height (temp_string) - 3) screen.blit_surface (life_image, score_font.string_width (temp_string) + 5, Window_height - life_image.height - 5) end -- draw "Pause" string if is_paused then create rectangle.make_from_position_and_size ((Window_width - 600) // 2, (Window_height - 180) // 2, 600, 180) rectangle.set_fill_color (create {EM_COLOR}.make_black) rectangle.set_filled (True) rectangle.set_rounded_corner_radius (20) rectangle.draw (screen) dj_moo_font.draw_string ("Pause", screen, Window_width // 2 - dj_moo_font.string_width ("Pause") // 2, Window_height // 2 - (dj_moo_font.string_height ("Pause") + 20) // 2) end end redraw is -- redraw all elements local password_font: EM_COLOR_TTF_FONT password_string: STRING rectangle: EM_RECTANGLE do draw_level if password /= Void then create password_font.make_from_ttf_font (standard_ttf_fonts.bitstream_vera_sans (22)) password_string := "Password: " + password password_font.set_color (create {EM_COLOR}.make_white) create rectangle.make_from_position_and_size (Window_width // 2 - password_font.string_width (password_string) // 2 - 5, Window_height // 2 - 30, password_font.string_width (password_string) + 10, password_font.string_height (password_string)) rectangle.set_fill_color (create {EM_COLOR}.make_black) rectangle.set_filled (True) rectangle.draw (screen) password_font.draw_string (password_string, screen, Window_width // 2 - password_font.string_width (password_string) // 2, Window_height // 2 - 30) end -- switch buffers screen.redraw end draw_active_items is -- draw images of all active items local spacing, x, y, image_width, image_height, remaining_effect_time, current_time, additional_image_width, additional_image_height: INTEGER do if number_of_active_items > 0 then current_time := last_time + frame_delay delete_run_off_item_display_effects (current_time) spacing := 5 image_width := item_images.item (1).width // 2 image_height := item_images.item (1).height // 2 x := Window_width // 2 - (number_of_active_items - 1 * spacing) - number_of_active_items * image_width y := Window_height - image_height - 2 -- calculate total additional x displacement because of item display effects from item_display_effects.start until item_display_effects.after loop x := x - (item_display_effects.item_for_iteration.second + Item_display_effect_duration - current_time) // Item_display_effect_duration item_display_effects.forth end if bars.first.is_large then remaining_effect_time := remaining_item_display_effect_time (Item_type_bar_large) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_bar_large), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if bars.first.is_small then remaining_effect_time := remaining_item_display_effect_time (Item_type_bar_small) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_bar_small), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if bars.first.is_fast then remaining_effect_time := remaining_item_display_effect_time (Item_type_bar_fast) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_bar_fast), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if bars.first.is_slow then remaining_effect_time := remaining_item_display_effect_time (Item_type_bar_slow) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_bar_slow), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if bars.first.is_inverted then remaining_effect_time := remaining_item_display_effect_time (Item_type_invert) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_invert), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if bars.first.is_frozen then remaining_effect_time := remaining_item_display_effect_time (Item_type_freeze) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_freeze), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if are_balls_invulnerable then remaining_effect_time := remaining_item_display_effect_time (Item_type_invulnerable) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_invulnerable), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if balls.first.is_fast then remaining_effect_time := remaining_item_display_effect_time (Item_type_ball_fast) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_ball_fast), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if balls.first.is_slow then remaining_effect_time := remaining_item_display_effect_time (Item_type_ball_slow) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_ball_slow), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if can_balls_fly_away then remaining_effect_time := remaining_item_display_effect_time (Item_type_fly_away) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_fly_away), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if bars.first.is_oversteering then remaining_effect_time := remaining_item_display_effect_time (Item_type_angle_high) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_angle_high), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if bars.first.is_understeering then remaining_effect_time := remaining_item_display_effect_time (Item_type_angle_low) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_angle_low), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if balls.first.is_transparent then remaining_effect_time := remaining_item_display_effect_time (Item_type_ball_transparent) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_ball_transparent), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if bars.first.is_transparent then remaining_effect_time := remaining_item_display_effect_time (Item_type_bar_transparent) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_bar_transparent), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if bars.first.is_sticky then remaining_effect_time := remaining_item_display_effect_time (Item_type_stick_on_bar) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_stick_on_bar), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if are_balls_destroying_everything then remaining_effect_time := remaining_item_display_effect_time (Item_type_destroy_all) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_destroy_all), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if are_grey_blocks_blended_out then remaining_effect_time := remaining_item_display_effect_time (Item_type_disappear) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_disappear), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if is_bad_item_in_effect then remaining_effect_time := remaining_item_display_effect_time (Item_type_bad_item) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_bad_item), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if bars.first.is_driving_left then remaining_effect_time := remaining_item_display_effect_time (Item_type_bar_drive_left) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_bar_drive_left), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end if bars.first.is_driving_right then remaining_effect_time := remaining_item_display_effect_time (Item_type_bar_drive_right) additional_image_width := image_width * remaining_effect_time // Item_display_effect_duration additional_image_height := image_height * remaining_effect_time // Item_display_effect_duration screen.draw_surface_stretched (item_images.item (Item_type_bar_drive_right), create {EM_ORTHOGONAL_RECTANGLE}.make_from_position_and_size (x, y - additional_image_height, image_width + additional_image_width, image_height + additional_image_height)) x := x + image_width + spacing + additional_image_width end end end feature -- Events handle_joystick_axis_event (event: EM_JOYSTICK_AXIS_EVENT) is -- Handle joystick axis event require else joystick_event_not_void: event /= Void do if event.is_axis = 0 then joystick_position := event.value end end handle_joystick_button_down_event (event: EM_JOYSTICK_BUTTON_EVENT) is -- Handle joystick button event require else joystick_event_not_void: event /= Void do if event.is_button = 0 then bars.first.start_balls_on_bar end end handle_update_event is -- handles everything of game physics local current_time: INTEGER do if not is_frame_counter_visibility_set then frame_counter.set_visible (False) is_frame_counter_visibility_set := True end frame_delay := time.ticks - last_time -- slowdown if framerate drops too much if frame_delay >= Max_frame_delay then frame_delay := Max_frame_delay current_time := time.ticks end if not is_paused then move_all handle_collisions handle_collision_with_wall if balls.is_empty then all_balls_lost end destroy_blocks end last_time := last_time + frame_delay if frame_delay >= Max_frame_delay then last_time := current_time end if remaining_blocks <= remaining_blocks_for_next_level then goto_next_level end end handle_key_down_event (a_keyboard_event: EM_KEYBOARD_EVENT) is -- Handle keyboard events. require else a_keyboard_event_not_void: a_keyboard_event /= Void local ball_cursor: DS_LINKED_LIST_CURSOR [BB_BALL] do if a_keyboard_event.key = a_keyboard_event.sdlk_f then frame_counter.set_visible (not frame_counter.is_visible) elseif a_keyboard_event.key = a_keyboard_event.sdlk_p then toggle_paused elseif a_keyboard_event.key = a_keyboard_event.sdlk_escape then back_to_menu -- allow some keyboard inputs only if game is not paused elseif not is_paused then if a_keyboard_event.key = a_keyboard_event.sdlk_right then pressed_keys.put (True, BB_keyboard_key_right) elseif a_keyboard_event.key = a_keyboard_event.sdlk_left then pressed_keys.put (True, BB_keyboard_key_left) -- start balls from bar and stop displaying password elseif a_keyboard_event.key = a_keyboard_Event.sdlk_space then bars.first.start_balls_on_bar set_password (Void) -- give up a life elseif a_keyboard_event.key = a_keyboard_event.sdlk_g then create ball_cursor.make (balls) from ball_cursor.start until ball_cursor.after loop collision_detector.remove (ball_cursor.item) ball_cursor.forth end balls.wipe_out end end end handle_key_up_event (a_keyboard_event: EM_KEYBOARD_EVENT) is -- Handle keyboard events require else a_keyboard_event_not_void: a_keyboard_event /= Void do if a_keyboard_event.key = a_keyboard_event.sdlk_right then pressed_keys.put (False, BB_keyboard_key_right) elseif a_keyboard_event.key = a_keyboard_event.sdlk_left then pressed_keys.put (False, BB_keyboard_key_left) end end feature {NONE} -- Internal Routines add_item_display_effect (item_type: INTEGER) is -- add an item display effect or replace it if it already exists do -- search if this effect is already active and remove it from item_display_effects.start until item_display_effects.after loop if item_display_effects.item_for_iteration.first = item_type then item_display_effects.remove_at else item_display_effects.forth end end item_display_effects.force_last (create {EM_PAIR [INTEGER, INTEGER]}.make (item_type, time.ticks)) end remaining_item_display_effect_time (item_type: INTEGER): INTEGER is -- remaining time of an item display effect of type `item_type' -- 0 is returned if no such effect is in progress do Result := 0 from item_display_effects.start until item_display_effects.after or Result > 0 loop if item_display_effects.item_for_iteration.first = item_type then Result := item_display_effects.item_for_iteration.second + Item_display_effect_duration - (last_time + frame_delay) if Result < 0 then Result := 0 end end item_display_effects.forth end end delete_run_off_item_display_effects (a_time: INTEGER) is -- delete item display effects which are run of at `a_time' in `item_display_effects' do from item_display_effects.start until item_display_effects.after loop if item_display_effects.item_for_iteration.second + Item_display_effect_duration < a_time then item_display_effects.remove_at else item_display_effects.forth end end end is_checksum_correct (a_file_name: STRING): BOOLEAN is -- check if checksum of file at "./levels/" + `a_file_name' is correct require a_file_name_not_void: a_file_name /= Void local file: PLAIN_TEXT_FILE checksum_file: PLAIN_TEXT_FILE line: STRING line_parts: LIST [STRING] checksum: INTEGER file_found: BOOLEAN checksum_generator: EM_CHECKSUM_GENERATOR do if level_set.is_equal ("custom") then Result := True else create file.make ("./levels/" + a_file_name) create checksum_file.make ("./levels/checksums.chk") if not file.exists or not checksum_file.exists then Result := False else -- search file in checksum file checksum_file.make_open_read ("./levels/checksums.chk") from checksum_file.start file_found := False until checksum_file.end_of_file or file_found loop checksum_file.read_line line := checksum_file.last_string line_parts := line.split ('=') if line_parts.first.is_equal (a_file_name) then checksum := line_parts.last.to_integer file_found := True end end -- check the checksum create checksum_generator checksum_generator.reset file.make_open_read ("./levels/" + a_file_name) file.start file.read_stream (file.count) checksum_generator.append_string (file.last_string) checksum_generator.generate_checksum Result := file_found and checksum_generator.checksum = checksum file.close checksum_file.close end end end feature {BB_OBJECT} -- Implementation levels: DS_LINKED_LIST[STRING] -- level list current_level: STRING -- current level balls: DS_LINKED_LIST [BB_BALL] -- list of all balls bars: DS_LINKED_LIST [BB_BAR] -- list of all bars last_time: INTEGER -- time of last frame frame_delay: INTEGER -- delay between last and current frame pressed_keys: ARRAY [BOOLEAN] -- currently pressed keys background: EM_BITMAP -- background image blocks: DS_LINKED_LIST [BB_BLOCK] -- list of all blocks blocks_to_destroy: DS_LINKED_LIST [BB_BLOCK] -- list of blocks which are destroyed at end of frame block_field: ARRAY2 [BOOLEAN] -- True if a block exists at corresponding position block_images: ARRAY [EM_BITMAP] -- block images score_font: EM_BMP_FONT -- font for drawing score life_image: EM_BITMAP -- image for remaining lifes last_score_extra_life: INTEGER -- score of last score extra life items: DS_LINKED_LIST [BB_ITEM] -- list of all items item_images: ARRAY [EM_BITMAP] -- list of all item images score_for_extra_life: INTEGER -- current score needed for an extra life remaining_blocks_for_next_level: INTEGER -- if there are only `remaining_blocks_for_next_level' blocks left then the next level is reached number_of_active_items: INTEGER -- number of currently active items dj_moo_font: EM_COLOR_TTF_FONT is -- font for "Pause" string once create Result.make_from_ttf_file ("./fonts/moo.ttf", 128) Result.set_color (create {EM_COLOR}.make_white) ensure Result /= Void end joystick: EM_JOYSTICK -- joystick container joystick_position: INTEGER -- current x axis position item_display_effects: DS_LINKED_LIST [EM_PAIR [INTEGER,INTEGER]] password: STRING -- password of current level level_set: STRING -- current level set is_frame_counter_visibility_set: BOOLEAN -- is frame counter visibility initialized invariant collision_detector_not_void: collision_detector /= Void balls_not_void: balls /= Void blocks_not_void: blocks /= Void blocks_to_destory_not_void: blocks_to_destroy /= Void block_field_not_void: block_field /= Void pressed_keys_not_void: pressed_keys /= Void life_image_not_void: life_image /= Void item_display_effects_not_void: item_display_effects /= Void end