indexing description: "[ Multiplayer Server for Multi Field Game ]" date: "$Date$" revision: "$Revision$" class BB_MP_MULTIFIELD_SERVER inherit BB_CONSTANTS EM_TIME_SINGLETON SHARED_BB_SETTINGS create make feature -- Initialization make (a_server: like server) is -- local file: PLAIN_TEXT_FILE do server := a_server create client_list.make server.subscribe_by_type_id (server.object_types.Bb_mp_game_started, agent on_game_started) server.subscribe_by_type_id (server.object_types.Bb_mp_add_block, agent on_add_block) server.subscribe_by_type_id (server.object_types.Bb_mp_item_effect, agent on_item_effect) server.subscribe_by_type_id (server.object_types.Bb_mp_score_report, agent on_score_report) server.subscribe_by_type_id (server.object_types.Bb_mp_finish_level, agent on_finish_level) server.subscribe_by_type_id (server.object_types.bb_mp_level_loaded, agent on_level_loaded) server.subscribe_by_type_id (server.object_types.Bb_mp_game_over, agent on_game_over (?)) server.subscribe_by_type_id (server.object_types.em_net_status_request, agent on_status_request (?)) create random.make random.set_seed (time.ticks) create file.make ("./levels/multiplayer.ord") if file.exists then create file.make_open_read ("./levels/multiplayer.ord") level_set := "Full" else create file.make_open_read ("./levels/simple_multiplayer.ord") level_set := "Simple" end create levels.make levels.set_equality_tester (create {UC_STRING_EQUALITY_TESTER}) 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 name_of_last_remote_effect_target := "" end start_game is -- send start_game messages require level_set: level /= Void local start_game_message: BB_MP_START_GAME do clients_ready := 0 -- notify clients to start the game start_game_message := server.object_types.create_bb_mp_start_game start_game_message.set_resend_time (Resend_time) start_game_message.set_resends_left (Number_of_resends) server.event_2pc_id_manager.set_unique_id_for_object (start_game_message) start_game_message.set_group (server.standard_group) start_game_message.publish end feature -- Status setting refuse_joins is -- refuse joins from now on do server.subscribe_by_type_id (server.object_types.Em_net_join_request, agent on_join_request (?)) end feature -- Access client (a_host_ip_port_string: STRING): BB_MP_PLAYER_INFO is -- player info of client with `a_host_ip_port_string' require a_host_ip_port_string /= Void local client_list_cursor: DS_LINKED_LIST_CURSOR [BB_MP_PLAYER_INFO] do create client_list_cursor.make (client_list) Result := Void from client_list_cursor.start until Result /= Void or client_list_cursor.after loop if client_list_cursor.item.ip_port_string.is_equal (a_host_ip_port_string) then Result := client_list_cursor.item end client_list_cursor.forth end end server: EM_NET_SERVER [BB_MP_OBJECT_TYPES] -- Server level: STRING -- current level level_set: STRING -- used level set feature -- Element change add_client (a_player_name: STRING; a_host_ip_port_string: STRING) is -- do client_list.force_last (create {BB_MP_PLAYER_INFO}.make (a_player_name, a_host_ip_port_string, 0)) end remove_client (a_host_ip_port_string: STRING) is -- remove client with `a_host_ip_port_string' from the client list do from client_list.start until client_list.after loop if client_list.item_for_iteration.ip_port_string.is_equal (a_host_ip_port_string) then client_list.remove_at client_list.go_after else client_list.forth end end end set_level (a_level: STRING) is -- set `level' require a_level /= Void do level := Void -- check if `a_level' is given as number if a_level.is_integer and a_level.to_integer <= levels.count then if a_level.to_integer <= levels.count and a_level.to_integer > 0 then levels.go_i_th (a_level.to_integer) level := levels.item_for_iteration.out bb_settings.set_start_level (level) bb_settings.write_user_settings else levels.go_after end -- search for the level else levels.start levels.search_forth (a_level) end -- check if it was a random level if levels.after then random.forth level := levels.item ((random.item \\ levels.count) + 1).out bb_settings.set_start_level ("Random") bb_settings.write_user_settings is_choosing_random_level := True -- it's not a random level else if level = Void then level := a_level.out end is_choosing_random_level := False end ensure level_not_void: a_level /= Void end feature -- Events on_finish_level (a_game_started: BB_MP_FINISH_LEVEL) is -- a client has finished the level local finish_level_message: BB_MP_FINISH_LEVEL do clients_ready := 0 -- notify clients to start the game finish_level_message := server.object_types.create_bb_mp_finish_level finish_level_message.set_resend_time (Resend_time) finish_level_message.set_resends_left (Number_of_resends) server.event_2pc_id_manager.set_unique_id_for_object (finish_level_message) finish_level_message.set_group (server.standard_group) finish_level_message.publish debug ("brick_breaker") io.put_string ("server received and sent finish level") io.new_line end if not is_choosing_random_level then levels.start levels.search_forth (level) if not levels.after then levels.forth end if levels.after then levels.start end set_level (levels.item_for_iteration) else set_level ("Random") end time.add_timed_procedure (agent calculate_winner, 5000) end on_game_started (a_game_started: BB_MP_GAME_STARTED) is -- a client has started the game do -- client_list.force_last (create {BB_MP_PLAYER_INFO}.make (a_game_started.player_name, a_game_started.updating_connection.socket_address.host_ip_port_as_string, 0)) clients_ready := clients_ready + 1 -- command clients to load a level if client_list.count = clients_ready then send_load_level end end on_level_loaded is -- a client has loaded a level local begin_game_message: BB_MP_BEGIN_GAME do -- client_list.force_last (create {BB_MP_PLAYER_INFO}.make (a_game_started.player_name, a_game_started.updating_connection.socket_address.host_ip_port_as_string, 0)) clients_ready := clients_ready + 1 -- notify clients that the game begins if client_list.count = clients_ready then begin_game_message := server.object_types.create_bb_mp_begin_game begin_game_message.set_resend_time (Resend_time) begin_game_message.set_resends_left (Number_of_resends) server.event_2pc_id_manager.set_unique_id_for_object (begin_game_message) begin_game_message.set_group (server.standard_group) begin_game_message.publish send_ranking end end on_add_block (an_add_block: BB_MP_ADD_BLOCK) is -- a client sent that he want's to create a block at another player local add_block_to_send: BB_MP_ADD_BLOCK do next_remote_effect_target (an_add_block.updating_connection.socket_address.host_ip_port_as_string) if last_remote_effect_target /= Void then add_block_to_send := server.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) server.event_2pc_id_manager.set_unique_id_for_object (add_block_to_send) add_block_to_send.set_number_of_hits (an_add_block.number_of_hits) add_block_to_send.set_group (last_remote_effect_target) add_block_to_send.publish end end on_score_report (a_score_report: BB_MP_SCORE_REPORT) is -- a client sent a score report do update_score_list (a_score_report.updating_connection.socket_address.host_ip_port_as_string, a_score_report.score, a_score_report.remaining_blocks, a_score_report.lifes) end on_item_effect (an_item_effect: BB_MP_ITEM_EFFECT) is -- a client sent that he wants to start an item effect at another player local an_item_effect_to_send: BB_MP_ITEM_EFFECT an_item_effect_report: BB_MP_ITEM_EFFECT_REPORT do next_remote_effect_target (an_item_effect.updating_connection.socket_address.host_ip_port_as_string) if last_remote_effect_target /= Void then an_item_effect_to_send := server.object_types.create_bb_mp_item_effect an_item_effect_to_send.set_resend_time (Resend_time) an_item_effect_to_send.set_resends_left (Number_of_resends) server.event_2pc_id_manager.set_unique_id_for_object (an_item_effect_to_send) an_item_effect_to_send.set_item_type (an_item_effect.item_type) an_item_effect_to_send.set_sender (an_item_effect.sender) an_item_effect_to_send.set_group (last_remote_effect_target) an_item_effect_to_send.publish an_item_effect_report := server.object_types.create_bb_mp_item_effect_report an_item_effect_report.set_resend_time (Resend_time) an_item_effect_report.set_resends_left (Number_of_resends) server.event_2pc_id_manager.set_unique_id_for_object (an_item_effect_report) an_item_effect_report.set_item_type (an_item_effect.item_type) an_item_effect_report.set_receiver (name_of_last_remote_effect_target) an_item_effect_report.set_group (server.group (an_item_effect.updating_connection.socket_address.host_ip_port_as_string)) an_item_effect_report.publish end end on_game_over (a_game_over: BB_MP_GAME_OVER) is -- a client sent that he went game over local a_game_over_to_send: BB_MP_GAME_OVER do a_game_over_to_send := server.object_types.create_bb_mp_game_over a_game_over_to_send.set_resend_time (Resend_time) a_game_over_to_send.set_resends_left (Number_of_resends) server.event_2pc_id_manager.set_unique_id_for_object (a_game_over_to_send) a_game_over_to_send.set_player_name (a_game_over.player_name) a_game_over_to_send.set_group (server.group ("not" + a_game_over.updating_connection.socket_address.host_ip_port_as_string)) a_game_over_to_send.publish end on_join_request (a_join_request: EM_NET_JOIN_REQUEST) is -- recieved join request local a_join_response: EM_NET_JOIN_RESPONSE do a_join_response := server.object_types.create_em_net_join_response a_join_response.set_resend_time (Resend_time) a_join_response.set_resends_left (Number_of_resends) server.event_2pc_id_manager.set_unique_id_for_object (a_join_response) a_join_response.deny_join a_join_response.set_group (a_join_request.group) a_join_response.publish end on_status_request (a_status_request: EM_NET_STATUS_REQUEST) is -- received status request local a_status_message: EM_NET_STATUS_MESSAGE do a_status_message := server.object_types.create_em_net_status_message server.event_2pc_id_manager.set_unique_id_for_object (a_status_message) a_status_message.set_group (a_status_request.group) a_status_message.set_status ("moo") a_status_message.publish end feature -- Network send_ranking is -- send ranking to all clients -- is called every `Ranking_resend_interval' milliseconds local a_ranking: BB_MP_RANKING do a_ranking := server.object_types.create_bb_mp_ranking a_ranking.set_resend_time (Resend_time) a_ranking.set_resends_left (Number_of_resends) server.event_2pc_id_manager.set_unique_id_for_object (a_ranking) a_ranking.set_ranking (client_list) a_ranking.set_group (server.standard_group) a_ranking.publish time.add_timed_procedure (agent send_ranking, Ranking_resend_interval) end feature -- Remote effects next_remote_effect_target (a_sender: STRING) is -- set `last_remote_effect_target' and `is_server_remote_effect_target' -- `a_sender' is host_ip_string of who sen't the remote effect, "self" means it was the server local target_player: INTEGER i: INTEGER random_value: INTEGER do if bb_settings.remote_item_effect_mode.is_equal ("Random") then random.forth target_player := random.item \\ client_list.count -- loop throug `client_list' until target player was found from i := 0 client_list.start until i = target_player or client_list.after loop i := i + 1 client_list.forth end check not client_list.after end if client_list.item_for_iteration.ip_port_string.is_equal (a_sender) then -- random value was the sender -> try again next_remote_effect_target (a_sender) else last_remote_effect_target := server.group (client_list.item_for_iteration.ip_port_string) name_of_last_remote_effect_target := client_list.item_for_iteration.player_name.out end elseif bb_settings.remote_item_effect_mode.is_equal ("All") then last_remote_effect_target := server.group ("not" + a_sender) name_of_last_remote_effect_target := "all" elseif bb_settings.remote_item_effect_mode.is_equal ("First") then client_list.start if client_list.first.ip_port_string.is_equal (a_sender) then client_list.forth end last_remote_effect_target := server.group (client_list.item_for_iteration.ip_port_string) name_of_last_remote_effect_target := client_list.item_for_iteration.player_name.out elseif bb_settings.remote_item_effect_mode.is_equal ("Distributed") then -- the first has a probability of 50%, the second 25%, the third 12,5% and so on -- if the random value is in the last part it's rolled again -- `a_sender' is not considered in this ranking from random.forth random_value := random.item \\ 10000 until random_value <= 10000 - (10000 / (2^(client_list.count - 1))) loop random.forth random_value := random.item \\ 10000 end client_list.start if client_list.item_for_iteration.ip_port_string.is_equal (a_sender) then client_list.forth end from i := 1 until random_value <= (10000 / (2 ^ i)).rounded or client_list.after loop if not client_list.item_for_iteration.ip_port_string.is_equal (a_sender) then random_value := random_value - (10000 / (2 ^ i)).rounded i := i + 1 end client_list.forth end check not_after:not client_list.after target_is_not_sender: not client_list.item_for_iteration.ip_port_string.is_equal (a_sender) end last_remote_effect_target := server.group (client_list.item_for_iteration.ip_port_string) name_of_last_remote_effect_target := client_list.item_for_iteration.player_name.out end end last_remote_effect_target: EM_NET_GROUP -- target group of the last remote effect, is Void if only the server is the target name_of_last_remote_effect_target: STRING -- player name of last remote effect target feature {NONE} -- Implementation update_score_list (a_client: STRING; a_score: INTEGER; a_remaining_blocks: INTEGER; a_lifes: INTEGER) is -- update client list corresponting to score local temp_player_info: BB_MP_PLAYER_INFO do from client_list.start until client_list.after loop if client_list.item_for_iteration.ip_port_string.is_equal (a_client) then -- it is a score increase if client_list.item_for_iteration.score < a_score then client_list.item_for_iteration.set_score (a_score) client_list.item_for_iteration.set_remaining_blocks (a_remaining_blocks) client_list.item_for_iteration.set_lifes (a_lifes) -- ensure sorting from temp_player_info := client_list.item_for_iteration client_list.back until client_list.before or not (client_list.item_for_iteration.score < a_score) loop client_list.item_for_iteration.swap_values (temp_player_info) temp_player_info := client_list.item_for_iteration client_list.back end -- it is a score decrease else client_list.item_for_iteration.set_score (a_score) client_list.item_for_iteration.set_remaining_blocks (a_remaining_blocks) -- ensure sorting from temp_player_info := client_list.item_for_iteration client_list.forth until client_list.after or not (client_list.item_for_iteration.score > a_score) loop client_list.item_for_iteration.swap_values (temp_player_info) temp_player_info := client_list.item_for_iteration client_list.forth end end client_list.finish end client_list.forth end ensure client_list_sorted: is_client_list_sorted end is_client_list_sorted: BOOLEAN is -- is client list sorted after score in decreasing order? require client_list_not_empty: not client_list.is_empty local i: INTEGER do Result := True from client_list.start i := client_list.item_for_iteration.score client_list.forth until client_list.after loop if client_list.item_for_iteration.score > i then Result := False client_list.go_after else i := client_list.item_for_iteration.score client_list.forth end end end send_load_level is -- notify clients to load a level require level_set: level /= Void local a_load_level: BB_MP_LOAD_LEVEL do a_load_level := server.object_types.create_bb_mp_load_level a_load_level.set_resend_time (Resend_time) a_load_level.set_resends_left (Number_of_resends) server.event_2pc_id_manager.set_unique_id_for_object (a_load_level) a_load_level.set_group (server.standard_group) a_load_level.set_level (level) a_load_level.publish clients_ready := 0 end calculate_winner is -- calculate winner of this game local most_remaining_blocks: INTEGER client_list_cursor: DS_LINKED_LIST_CURSOR [BB_MP_PLAYER_INFO] a_client: BB_MP_PLAYER_INFO winner_score: INTEGER found_an_overall_winner: BOOLEAN do create client_list_cursor.make (client_list) -- find highest number of remaining blocks from client_list_cursor.start most_remaining_blocks := 0 until client_list_cursor.after loop if client_list_cursor.item.remaining_blocks > most_remaining_blocks then most_remaining_blocks := client_list_cursor.item.remaining_blocks end client_list_cursor.forth end from client_list_cursor.start until client_list_cursor.after loop a_client := client_list_cursor.item update_score_list (a_client.ip_port_string, a_client.score + (most_remaining_blocks - a_client.remaining_blocks) * Bonus_score_for_remaining_block, a_client.remaining_blocks, a_client.lifes) client_list_cursor.forth end winner_score := client_list.first.score from client_list_cursor.start found_an_overall_winner := False until client_list_cursor.after or client_list_cursor.item.score < winner_score loop client_list_cursor.item.set_number_of_wins (client_list_cursor.item.number_of_wins + 1) if client_list_cursor.item.number_of_wins >= bb_settings.number_of_games_to_win then found_an_overall_winner := True end client_list_cursor.forth end send_game_result (most_remaining_blocks) if found_an_overall_winner then time.add_timed_procedure (agent send_end_game, 5000) else time.add_timed_procedure (agent send_load_level, 5000) end end send_game_result (most_remaining_blocks: INTEGER) is -- send game result to all clients local a_game_result: BB_MP_GAME_RESULT do a_game_result := server.object_types.create_bb_mp_game_result a_game_result.set_resend_time (Resend_time) a_game_result.set_resends_left (Number_of_resends) server.event_2pc_id_manager.set_unique_id_for_object (a_game_result) a_game_result.set_game_result (client_list) a_game_result.set_most_remaining_blocks (most_remaining_blocks) a_game_result.set_group (server.standard_group) a_game_result.publish debug ("brick_breaker") io.put_string ("sent game result") io.new_line end end send_end_game is -- send game result to all clients local an_end_game: BB_MP_END_GAME do an_end_game := server.object_types.create_bb_mp_end_game an_end_game.set_resend_time (Resend_time) an_end_game.set_resends_left (Number_of_resends) server.event_2pc_id_manager.set_unique_id_for_object (an_end_game) an_end_game.set_group (server.standard_group) an_end_game.publish debug ("brick_breaker") io.put_string ("Sent end game") io.new_line end end clients_ready: INTEGER -- number of players which are ready to start random: RANDOM levels: DS_LINKED_LIST [STRING] -- list of levels is_choosing_random_level: BOOLEAN -- are levels chosen by random? feature {BB_MP_MULTIFIELD_CLIENT_LEVEL_SCENE} -- Implementation client_list: DS_LINKED_LIST [BB_MP_PLAYER_INFO] -- list of connected clients end