indexing description: "[ Multi Field Multiplayer Level Scene for servers ]" date: "$Date$" revision: "$Revision$" class BB_MP_MULTIFIELD_CLIENT_LEVEL_SCENE inherit BB_LEVEL_SCENE rename make as make_level_scene redefine initialize_scene, redraw, set_score, start_item_effect, handle_key_down_event, handle_key_up_event, goto_next_level, game_over, back_to_menu, start_cheater_scene, write_savegame, subtract_life end EM_SHARED_STANDARD_FONTS export {NONE} all end create make feature {NONE} -- Initialization make (a_client: like client; a_server: like server; an_options_object: like options) is -- Initialize `Current'. require a_client /= Void an_options_object /= Void local sp_difficulty: STRING do client := a_client server := a_server bitmap_factory.create_bitmap_from_image ("./images/backgrounds/loading.jpg") loading_image := bitmap_factory.last_bitmap client.subscribe_by_type_id (client.object_types.bb_mp_game_result, agent on_game_result (?)) client.subscribe_by_type_id (client.object_types.Bb_mp_begin_game,agent on_begin_game) client.subscribe_by_type_id (client.object_types.bb_mp_add_block, agent on_add_block (?)) client.subscribe_by_type_id (client.object_types.bb_mp_item_effect, agent on_item_effect (?)) client.subscribe_by_type_id (client.object_types.bb_mp_item_effect_report, agent on_item_effect_report (?)) client.subscribe_by_type_id (client.object_types.bb_mp_ranking, agent on_ranking (?)) client.subscribe_by_type_id (client.object_types.bb_mp_finish_level, agent on_finish_level (?)) client.subscribe_by_type_id (client.object_types.bb_mp_load_level, agent on_load_level (?)) client.subscribe_by_type_id (client.object_types.bb_mp_end_game, agent back_to_menu) client.subscribe_by_type_id (client.object_types.Bb_mp_game_over, agent on_game_over (?)) client.timeout_event.subscribe (agent on_client_lost_connection_to_server (?)) options := an_options_object remaining_score_for_enemy_block := options.score_for_enemy_block if server /= Void then -- what is the difference between having or not having this line? -- server.server.timeout_event.subscriptions.wipe_out server.server.timeout_event.subscribe (agent on_server_lost_connection_to_client (?)) end -- change difficulty initialize level and change it back sp_difficulty := bb_settings.difficulty.out bb_settings.set_difficulty (options.difficulty) make_level_scene bb_settings.set_difficulty (sp_difficulty.out) is_drawing_ranking := False is_drawing_game_result := False is_drawing_item_report := False is_drawing_game_over_report := False item_report_target := "" game_over_report := "" end initialize_scene is -- Initialize scene local an_options_request: BB_MP_OPTIONS_REQUEST do Precursor has_game_begun := False -- check if the game was started before the options were received if options = Void then an_options_request := client.object_types.create_bb_mp_options_request an_options_request.set_resend_time (Resend_time) an_options_request.set_resends_left (Number_of_resends) client.event_2pc_id_manager.set_unique_id_for_object (an_options_request) an_options_request.set_group (client.standard_group) an_options_request.publish else send_game_started_message end end send_game_started_message is -- notify server that the game is started local game_started_message: BB_MP_GAME_STARTED do game_started_message := client.object_types.create_bb_mp_game_started game_started_message.set_resend_time (Resend_time) game_started_message.set_resends_left (Number_of_resends) client.event_2pc_id_manager.set_unique_id_for_object (game_started_message) game_started_message.set_group (client.standard_group) game_started_message.set_player_name (bb_settings.nickname) game_started_message.publish end feature -- Events on_begin_game is -- server sent event that the game begins do has_game_begun := True reset_score end on_load_level (a_load_level: BB_MP_LOAD_LEVEL) is -- load level local a_level_loaded: BB_MP_LEVEL_LOADED a_ball: BB_BALL do random.set_seed (time.ticks) clear_level set_everything_to_normal current_level := a_load_level.level.out load_level ("./levels/" + a_load_level.level + ".lvl") if difficulty = difficulty_foolproof then lifes := Start_lifes_foolproof elseif difficulty = difficulty_easy then lifes := Start_lifes_easy elseif difficulty = difficulty_normal then lifes := Start_lifes_normal elseif difficulty = difficulty_hard then lifes := Start_lifes_hard elseif difficulty = difficulty_insane then lifes := Start_lifes_insane else lifes := Start_lifes_normal end -- create a new ball create a_ball.make_on_bar (bars.first) -- notify server that level is loaded a_level_loaded := client.object_types.create_bb_mp_level_loaded a_level_loaded.set_resend_time (Resend_time) a_level_loaded.set_resends_left (Number_of_resends) client.event_2pc_id_manager.set_unique_id_for_object (a_level_loaded) a_level_loaded.set_group (client.standard_group) a_level_loaded.publish is_drawing_game_result := False end on_finish_level (a_finish_level: BB_MP_FINISH_LEVEL) is -- server sent event that other player finished level do has_game_begun := False pressed_keys.put (False, BB_keyboard_key_right) pressed_keys.put (False, BB_keyboard_key_left) is_drawing_ranking := True send_score_report stop_all_balls debug ("brick_breaker") io.put_string ("client received finish level") io.new_line end end on_game_result (a_game_result: BB_MP_GAME_RESULT) is -- server sent game result do ranking := a_game_result.game_result most_remaining_blocks := a_game_result.most_remaining_blocks is_drawing_ranking := False is_drawing_game_result := True debug ("brick_breaker") io.put_string ("received game result") io.new_line end end on_add_block (an_add_block: BB_MP_ADD_BLOCK) is -- server sent event to add a block do remote_add_block (an_add_block.number_of_hits) end on_item_effect (an_item_effect: BB_MP_ITEM_EFFECT) is -- server sent a remote item effect do is_local_item_effect := True start_item_effect (an_item_effect.item_type) is_local_item_effect := False -- setup item report item_report_target := " from " + an_item_effect.sender item_report_type := "received " item_report_item_type := an_item_effect.item_type is_drawing_item_report := True time.remove_timed_procedure (agent stop_drawing_item_report) time.add_timed_procedure (agent stop_drawing_item_report, Item_report_duration) ensure is_local_item_effect = False end on_item_effect_report (an_item_effect_report: BB_MP_ITEM_EFFECT_REPORT) is -- handle item effect report do item_report_target := " to " + an_item_effect_report.receiver.out item_report_type := "sent " item_report_item_type := an_item_effect_report.item_type is_drawing_item_report := True time.remove_timed_procedure (agent stop_drawing_item_report) time.add_timed_procedure (agent stop_drawing_item_report, Item_report_duration) end on_ranking (a_ranking: BB_MP_RANKING) is -- server sent ranking do ranking := a_ranking.ranking end on_server_lost_connection_to_client (a_connection: EM_NET_CONNECTION) is -- remove `a_connection' and corresponding client from all lists if it lost connection to server do server.server.delete_connection (a_connection) server.server.remove_group_by_name (a_connection.socket_address.host_ip_port_as_string) server.server.remove_group_by_name ("not" + a_connection.socket_address.host_ip_port_as_string) server.remove_client (a_connection.socket_address.host_ip_port_as_string) debug ("brick_breaker") io.put_string ("Connection lost: " + a_connection.socket_address.host_ip_port_as_string) io.put_new_line end if server.client_list.count <= 1 then back_to_menu end end on_client_lost_connection_to_server (a_connection: EM_NET_CONNECTION) is -- client lost connection to server do debug ("brick_breaker") io.put_string ("Lost connection to server") io.put_new_line end back_to_menu end on_game_over (a_game_over: BB_MP_GAME_OVER) is -- server sent that a player is game over do is_drawing_game_over_report := True game_over_report := a_game_over.player_name + " is game over" time.remove_timed_procedure (agent stop_drawing_game_over_report) time.add_timed_procedure (agent stop_drawing_game_over_report, Game_over_report_duration) end feature -- Drawing redraw is -- redraw scene do if has_game_begun then draw_level else loading_image.draw (screen) score_font.draw_string ("Waiting for players...", screen, bb_window_width // 2 - score_font.string_width ("Waiting for players...") // 2, bb_window_height // 2 - score_font.string_height ("Waiting for players...") // 2) end if is_drawing_ranking then draw_ranking end if is_drawing_game_result then draw_game_result end if is_drawing_game_over_report then draw_game_over_report elseif is_drawing_item_report then draw_item_report end screen.redraw end draw_ranking is -- draw ranking local i: INTEGER rectangle: EM_RECTANGLE ranking_position_y: INTEGER do ranking_position_y := (bb_window_height - ((ranking.count + 1) * Ranking_line_spacing)) // 2 create rectangle.make_from_position_and_size (Ranking_position_x - Ranking_border, Ranking_position_y - Ranking_border, Ranking_position_x_end - Ranking_position_x + 2 * Ranking_border, (ranking.count + 1) * Ranking_line_spacing + 2 * Ranking_border) rectangle.set_fill_color (create {EM_COLOR}.make_black) rectangle.set_filled (True) rectangle.draw (screen) ranking_font.draw_string ("Player",screen, Ranking_position_x_name, ranking_position_y) ranking_font.draw_string ("Score", screen, Ranking_position_x_score_end - ranking_font.string_width ("Score"), ranking_position_y) ranking_font.draw_string ("Blocks", screen, Ranking_position_x_blocks_end - ranking_font.string_width ("Blocks"), ranking_position_y) ranking_font.draw_string ("Lifes", screen, Ranking_position_x_lifes_end - ranking_font.string_width ("Lifes"), ranking_position_y) ranking_font.draw_string ("Wins", screen, Ranking_position_x_wins_end - ranking_font.string_width ("Wins"), ranking_position_y) from ranking.start i := 1 until ranking.after loop ranking_font.draw_string (i.out + ".", screen, Ranking_position_x, ranking_position_y + i * Ranking_line_spacing) ranking_font.draw_string (ranking.item_for_iteration.player_name, screen, ranking_position_x_name, ranking_position_y + i * Ranking_line_spacing) ranking_font.draw_string (ranking.item_for_iteration.score.out, screen, Ranking_position_x_score_end - ranking_font.string_width (ranking.item_for_iteration.score.out), ranking_position_y + i * Ranking_line_spacing) ranking_font.draw_string (ranking.item_for_iteration.remaining_blocks.out, screen, Ranking_position_x_blocks_end - ranking_font.string_width (ranking.item_for_iteration.remaining_blocks.out), ranking_position_y + i * Ranking_line_spacing) ranking_font.draw_string (ranking.item_for_iteration.lifes.out, screen, Ranking_position_x_lifes_end - ranking_font.string_width (ranking.item_for_iteration.lifes.out), ranking_position_y + i * Ranking_line_spacing) ranking_font.draw_string (ranking.item_for_iteration.number_of_wins.out, screen, Ranking_position_x_wins_end - ranking_font.string_width (ranking.item_for_iteration.number_of_wins.out), ranking_position_y + i * Ranking_line_spacing) i := i + 1 ranking.forth end end draw_game_result is -- draw game result local i: INTEGER rectangle: EM_RECTANGLE game_result_position_y: INTEGER temp_score: INTEGER do game_result_position_y := (bb_window_height - ((ranking.count + 1) * Ranking_line_spacing)) // 2 create rectangle.make_from_position_and_size (Game_result_position_x - Ranking_border, game_result_position_y - Ranking_border, Game_result_position_x_end - Game_result_position_x + 2 * Ranking_border, (ranking.count + 1) * Ranking_line_spacing + 2 * Ranking_border) rectangle.set_fill_color (create {EM_COLOR}.make_black) rectangle.set_filled (True) rectangle.draw (screen) ranking_font.draw_string ("Player",screen, Game_result_position_x_name, game_result_position_y) ranking_font.draw_string ("Score", screen, Game_result_position_x_score_end - ranking_font.string_width ("Score"), game_result_position_y) ranking_font.draw_string ("Bonus", screen, Game_result_position_x_bonus_score_end - ranking_font.string_width ("Bonus"), game_result_position_y) ranking_font.draw_string ("Total", screen, Game_result_position_x_total_score_end - ranking_font.string_width ("Total"), game_result_position_y) ranking_font.draw_string ("Wins", screen, Game_result_position_x_wins_end - ranking_font.string_width ("Wins"), game_result_position_y) from ranking.start i := 1 until ranking.after loop ranking_font.draw_string (i.out + ".", screen, Game_result_position_x, game_result_position_y + i * Ranking_line_spacing) ranking_font.draw_string (ranking.item_for_iteration.player_name, screen, Game_result_position_x_name, game_result_position_y + i * Ranking_line_spacing) -- score temp_score := ranking.item_for_iteration.score - (most_remaining_blocks - ranking.item_for_iteration.remaining_blocks) * Bonus_score_for_remaining_block ranking_font.draw_string (temp_score.out, screen, Game_result_position_x_score_end - ranking_font.string_width (temp_score.out), game_result_position_y + i * Ranking_line_spacing) -- bonus temp_score := (most_remaining_blocks - ranking.item_for_iteration.remaining_blocks) * Bonus_score_for_remaining_block ranking_font.draw_string ("+" + temp_score.out, screen, Game_result_position_x_bonus_score_end - ranking_font.string_width ("+" + temp_score.out), game_result_position_y + i * Ranking_line_spacing) -- total temp_score := ranking.item_for_iteration.score ranking_font.draw_string (temp_score.out, screen, Game_result_position_x_total_score_end - ranking_font.string_width (temp_score.out), game_result_position_y + i * Ranking_line_spacing) ranking_font.draw_string (ranking.item_for_iteration.number_of_wins.out, screen, Game_result_position_x_wins_end - ranking_font.string_width (ranking.item_for_iteration.number_of_wins.out), game_result_position_y + i * Ranking_line_spacing) i := i + 1 ranking.forth end end stop_drawing_item_report is -- stop drawing item report do is_drawing_item_report := False end draw_item_report is -- draw item report local x: INTEGER image_size: INTEGER rectangle: EM_RECTANGLE do image_size := item_report_font.string_height (item_report_target) -- draw black rectangle as background x := bb_window_width - score_font.string_width (score.out) - 20 - item_report_font.string_width (item_report_target) - image_size - item_report_font.string_width (item_report_type) create rectangle.make_from_coordinates (x, bb_window_height - image_size, bb_window_width - score_font.string_width (score.out) - 20, bb_window_height) rectangle.set_fill_color (create {EM_COLOR}.make_black) rectangle.set_filled (True) rectangle.draw (screen) -- draw item report x := bb_window_width - score_font.string_width (score.out) - 20 - item_report_font.string_width (item_report_target) item_report_font.draw_string (item_report_target, screen, x, bb_window_height - image_size) x := x - image_size screen.draw_surface_stretched (item_images.item (item_report_item_type), create {EM_RECTANGLE}.make_from_position_and_size (x, bb_window_height - image_size, image_size, image_size)) x := x - item_report_font.string_width (item_report_type) item_report_font.draw_string (item_report_type, screen, x, bb_window_height - image_size) end stop_drawing_game_over_report is -- stop drawing game over report do is_drawing_game_over_report := False end draw_game_over_report is -- draw game over report local rectangle: EM_RECTANGLE x, y: INTEGER do x := bb_window_width - score_font.string_width (score.out) - 25 - item_report_font.string_width (game_over_report) y := bb_window_height - item_report_font.string_height (game_over_report) create rectangle.make_from_position_and_size (x - 5, y, item_report_font.string_width (game_over_report) + 10, item_report_font.string_height (game_over_report)) rectangle.set_filled (True) rectangle.set_fill_color (create {EM_COLOR}.make_black) rectangle.draw (screen) item_report_font.draw_string (game_over_report, screen, x, y) end feature -- Remote effects start_item_effect (an_item_type: INTEGER) is -- check if an item effect should be started locally or sent to the server local an_item_effect: BB_MP_ITEM_EFFECT do if is_local_item_effect then -- change behaviour of next level item if an_item_type = Item_type_next_level then set_score (score + (5000 * score_multiplier).rounded) play_sound (Sound_next_level) else precursor (an_item_type) end else random.forth -- send item effect if random.item \\ 100 < options.remote_item_effect_chance then an_item_effect := client.object_types.create_bb_mp_item_effect an_item_effect.set_resend_time (Resend_time) an_item_effect.set_resends_left (Number_of_resends) client.event_2pc_id_manager.set_unique_id_for_object (an_item_effect) an_item_effect.set_item_type (an_item_type) an_item_effect.set_sender (bb_settings.nickname) an_item_effect.set_group (client.standard_group) an_item_effect.publish else if an_item_type = Item_type_bad_item or an_item_type = Item_type_good_item or an_item_type = Item_type_russian_roulette then -- ensure that all effects are activated locally is_local_item_effect := True start_item_effect (an_item_type) is_local_item_effect := False elseif an_item_type = Item_type_next_level then set_score (score + (5000 * score_multiplier).rounded) play_sound (Sound_next_level) else precursor (an_item_type) end end end end remote_add_block (a_number_of_hits: INTEGER) is -- add a block at random position with `number_of_hits' hits or add that much hits if a block already exists at that position local row, column: INTEGER a_block: BB_BLOCK block_found: BOOLEAN block_cursor: DS_LINKED_LIST_CURSOR[BB_BLOCK] number_of_hits: INTEGER do if a_number_of_hits > 9 then number_of_hits := 9 else number_of_hits := a_number_of_hits end random.forth row := (random.item \\ Number_of_rows) + 1 random.forth column := (random.item \\ Number_of_columns) + 1 -- if there exists a block if block_field.item (row, column) then from create block_cursor.make (blocks) block_cursor.start block_found := False until block_cursor.after or block_found or not block_field.item (row, column) loop -- if it's the right block if block_cursor.item.row = row and block_cursor.item.column = column then -- if the block is full or indestructible if block_cursor.item.number_of_hits < 0 or block_cursor.item.number_of_hits >= 9 then -- do nothing -- if it will be full elseif block_cursor.item.number_of_hits + number_of_hits > 9 then block_cursor.item.set_number_of_hits (9) play_sound (Sound_add_block) else block_cursor.item.set_number_of_hits (block_cursor.item.number_of_hits + number_of_hits) play_sound (Sound_add_block) end block_cursor.finish end block_cursor.forth end elseif are_grey_blocks_blended_out then -- check if there is a block from create block_cursor.make (blocks) block_cursor.start block_found := False until block_cursor.after or block_found loop if block_cursor.item.row = row and block_cursor.item.column = column then block_found := True end block_cursor.forth end if not block_found then create a_block.make (row, column, number_of_hits, Current) play_sound (Sound_add_block) end else create a_block.make (row, column, number_of_hits, Current) play_sound (Sound_add_block) end send_score_report end send_score_report is -- send a score report to server local a_score_report: BB_MP_SCORE_REPORT do a_score_report := client.object_types.create_bb_mp_score_report a_score_report.set_score (score) a_score_report.set_lifes (lifes) -- TODO: wrong number of remaining blocks if an undestroyable block is part of blocks_to_destroy a_score_report.set_remaining_blocks (remaining_blocks - blocks_to_destroy.count) a_score_report.set_resend_time (Resend_time) a_score_report.set_resends_left (Number_of_resends) client.event_2pc_id_manager.set_unique_id_for_object (a_score_report) a_score_report.set_group (client.standard_group) a_score_report.publish end feature -- Status setting set_score (an_integer: INTEGER) is -- set `score' to `an_integer' and send add_block events local old_score: INTEGER number_of_hits: INTEGER add_block_to_send: BB_MP_ADD_BLOCK do old_score := score precursor (an_integer) from remaining_score_for_enemy_block := remaining_score_for_enemy_block - (score - old_score) number_of_hits := 0 until remaining_score_for_enemy_block > 0 loop number_of_hits := number_of_hits + 1 remaining_score_for_enemy_block := remaining_score_for_enemy_block + options.score_for_enemy_block end if number_of_hits > 0 then add_block_to_send := client.object_types.create_bb_mp_add_block add_block_to_send.set_resend_time (Resend_time) add_block_to_send.set_resends_left (Number_of_resends) client.event_2pc_id_manager.set_unique_id_for_object (add_block_to_send) add_block_to_send.set_number_of_hits (number_of_hits) add_block_to_send.set_group (client.standard_group) add_block_to_send.publish end send_score_report -- goto next level if someone has to many points, to avoid never ending games if score > 1000000 then goto_next_level end end reset_score is -- reset score to 0 do score := 0 last_score_extra_life := 0 remaining_blocks_for_next_level := 0 send_score_report end feature -- Commands goto_next_level is -- goto next level local finish_level_message: BB_MP_FINISH_LEVEL do if has_game_begun then has_game_begun := False stop_all_balls -- notify server level is finished finish_level_message := client.object_types.create_bb_mp_finish_level finish_level_message.set_resend_time (Resend_time) finish_level_message.set_resends_left (Number_of_resends) client.event_2pc_id_manager.set_unique_id_for_object (finish_level_message) finish_level_message.set_group (client.standard_group) finish_level_message.publish end end game_over is -- all lifes lost local a_game_over: BB_MP_GAME_OVER do set_score (score - 50000) remaining_score_for_enemy_block := 0 last_score_extra_life := score if difficulty = difficulty_foolproof then lifes := Start_lifes_foolproof elseif difficulty = difficulty_easy then lifes := Start_lifes_easy elseif difficulty = difficulty_normal then lifes := Start_lifes_normal elseif difficulty = difficulty_hard then lifes := Start_lifes_hard elseif difficulty = difficulty_insane then lifes := Start_lifes_insane else lifes := Start_lifes_normal end a_game_over := client.object_types.create_bb_mp_game_over a_game_over.set_player_name (bb_settings.nickname) a_game_over.set_resend_time (Resend_time) a_game_over.set_resends_left (Number_of_resends) client.event_2pc_id_manager.set_unique_id_for_object (a_game_over) a_game_over.set_group (client.standard_group) a_game_over.publish end back_to_menu is -- go back to menu do stop_sound (Sound_invulnerable) play_music ("./bgm/menu.ogg") client.disable_auto_transmit client.disable_time_sync client.close if server /= Void then server.server.disable_auto_transmit server.server.disable_time_sync server.server.close set_next_scene (create {BB_HOST_GAME}.make) else set_next_scene (create {BB_JOIN_GAME}.make) end start_next_scene end start_cheater_scene is -- start cheater scene do client.disable_auto_transmit client.disable_time_sync client.close if server /= Void then server.server.disable_auto_transmit server.server.disable_time_sync server.server.close end Precursor end write_savegame is -- no safegame should be written in multiplayer mode do end subtract_life is -- subtract a life do precursor send_score_report end feature -- Event handling handle_key_down_event (a_keyboard_event: EM_KEYBOARD_EVENT) is -- Handle keyboard events. do -- leaving the game should always be possible if a_keyboard_event.key = a_keyboard_event.sdlk_escape then back_to_menu elseif has_game_begun then if a_keyboard_event.key = a_keyboard_event.sdlk_tab then is_drawing_ranking := True end if a_keyboard_event.key = a_keyboard_event.sdlk_p then -- disable pausing in multi player mode else precursor (a_keyboard_event) end end end handle_key_up_event (a_keyboard_event: EM_KEYBOARD_EVENT) is -- Handle keyboard events. do if has_game_begun then if a_keyboard_event.key = a_keyboard_event.sdlk_tab then is_drawing_ranking := False end precursor (a_keyboard_event) end end feature {NONE} -- Implementation client: EM_NET_CLIENT [BB_MP_OBJECT_TYPES] -- Client server: BB_MP_MULTIFIELD_SERVER -- Server has_game_begun: BOOLEAN -- has the game begun? loading_image: EM_BITMAP -- loading image options: BB_MP_OPTIONS -- options for this game remaining_score_for_enemy_block: INTEGER -- how much score must be achieved to add the next remote block? is_local_item_effect: BOOLEAN -- must the current item effect be activated locally? ranking: DS_LINKED_LIST [BB_MP_PLAYER_INFO] -- current ranking is_drawing_ranking: BOOLEAN -- is ranking currently drawed? is_drawing_game_result: BOOLEAN -- is game result currently drawed? most_remaining_blocks: INTEGER -- number of blocks of player with most remaining blocks of last game is_drawing_item_report: BOOLEAN -- is currently an item effect report drawn? item_report_target: STRING -- name of player who is involved in item report concatenated to "to " or "from " item_report_item_type: INTEGER -- item type of item which is involved in item report item_report_type: STRING -- "received" or "sent" is_drawing_game_over_report: BOOLEAN -- is currently a game over report drawn? game_over_report: STRING -- game over report text feature {NONE} -- Implementation ranking_font: EM_COLOR_TTF_FONT is -- font for ranking display once create Result.make_from_ttf_font (standard_ttf_fonts.bitstream_vera_sans (Ranking_font_size)) Result.set_color (create {EM_COLOR}.make_white) ensure Result /= Void end item_report_font: EM_COLOR_TTF_FONT is -- font for item reports once create Result.make_from_ttf_font (standard_ttf_fonts.bitstream_vera_sans (16)) Result.set_color (create {EM_COLOR}.make_white) ensure Result /= Void end stop_all_balls is -- stop all balls do from balls.start until balls.after loop balls.item_for_iteration.set_stopped (True) balls.forth end end end