Created
May 19, 2017 12:45
-
-
Save afa/9da4911e9ecfd93ae5f13aa95b9039e3 to your computer and use it in GitHub Desktop.
celluloid game
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Game | |
| include Celluloid | |
| include Celluloid::IO | |
| include Celluloid::Internals::Logger | |
| include Celluloid::Notifications | |
| extend Forwardable | |
| finalizer :finalizer | |
| def_delegators :int_state, :stage, :step, :total_steps, :step_status, :statements, :setting | |
| attr_accessor :name, :online | |
| def self.create params = {} | |
| uuid = build(params) | |
| args = { uuid: uuid }.merge(params[:server_setup] ? { server_setup: params[:server_setup] } : {}).merge(start: params[:start]) | |
| Center.current.async.to_supervise as: :"game_#{uuid}", | |
| type: Game, | |
| args: [args] | |
| end | |
| def self.build(params = {}) | |
| uuid = UUID.new.generate | |
| time = if params[:start_at] | |
| Time.at(params[:start_at].to_i).to_i | |
| elsif params[:start] | |
| Time.at(params[:start][:time].to_i).to_i | |
| else | |
| Time.now.to_i + 300 | |
| end | |
| store = Store::Game.create( | |
| mongo_id: params[:id], | |
| name: params[:name], | |
| uuid: uuid, | |
| company: params[:company], | |
| country: params[:country], | |
| description: params[:description], | |
| industry: params[:industry], | |
| state: params[:state], | |
| time_zone: params[:time_zone], | |
| start_at: time | |
| ) | |
| if params[:players] | |
| params[:players].each_with_index do |pl, idx| | |
| dat = pl.merge(game_uuid: uuid, order: idx + 1) | |
| mid = dat.delete(:id) | |
| dat[:mongo_id] = mid | |
| Player.build dat | |
| end | |
| end | |
| sett = Store::Setting.for_game(uuid) | |
| sett.update data: sett.data.merge(params[:settings]) if params[:settings] | |
| args = { uuid: uuid, start_at: time } | |
| args.merge!(params[:server_setup]) if params[:server_setup].is_a?(Hash) | |
| if store.start_at.to_i > Time.now.to_i | |
| Center.current.to_supervise as: "game_#{uuid}", type: Game, args: [args] | |
| end | |
| uuid | |
| end | |
| def self.results_for(id) | |
| store = Store::Game.find(uuid: id).first | |
| players = Store::Player.find(game_uuid: id).all.to_a | |
| setting = Store::Setting.find(game_uuid: id).first | |
| statements = Store::Statement.find(game_uuid: id).all.to_a | |
| { | |
| game: { name: store.name, id: store.mongo_id, uuid: store.uuid }, | |
| players: players.map do |pl| | |
| { | |
| name: pl.name, | |
| mangled_name: "Player_#{pl.position}", | |
| pitcher_score: pl.pitcher_score, | |
| catcher_score: pl.catcher_score, | |
| uuid: pl.uuid, | |
| position: pl.position | |
| } | |
| end, | |
| settings: setting.data, | |
| statements: statements.inject(s: [], w: [], o: [], t: []) { |rez, st| } # TODO: wtf? | |
| } | |
| end | |
| def int_state | |
| Actor[:"state_#{@uuid}"] | |
| end | |
| def initialize(params = {}) | |
| @online = false | |
| @uuid = params[:uuid] | |
| @server_setup = params[:server_setup] | |
| sgame = Store::Game.find(uuid: @uuid).first | |
| @start_at = sgame.start_at | |
| centre = Center.current | |
| centre.to_supervise as: :"admin_logger_#{@uuid}", type: AdminLogger, args: [{ game_uuid: @uuid }] | |
| centre.to_supervise as: :"state_#{@uuid}", type: State, args: [{ game_uuid: @uuid }] | |
| self.name = sgame.name | |
| state = int_state | |
| centre.to_supervise(as: :"players_#{@uuid}", type: Players, args: [{ game_uuid: @uuid }]) | |
| players = Actor[:"players_#{@uuid}"] | |
| centre.to_supervise as: :"timers_#{@uuid}", type: Timings, args: [{ game_uuid: @uuid }] | |
| Timings::Start.instance(@uuid).set_time @start_at | |
| state.state = if Timings::Start.instance(@uuid).next_time | |
| :waiting | |
| elsif Timings::Start.instance(@uuid).at | |
| :started | |
| else | |
| :waiting | |
| end | |
| Control.current.publish_control((params.has_key?(:players) ? { players: players.players.map { |p| { name: p.name, url: "#{@server_setup[:url]}/game/#{p.uuid}", uuid: p.uuid, email: p.email } } } : { }).merge(type: 'status', uuid: @uuid, replly_to: 'create')) | |
| Control.current.add_game(@uuid) | |
| state.add_game @uuid | |
| subscribe :save_game_data, :save_game_data | |
| info "done init for #{@uuid}" | |
| async.run | |
| end | |
| def save_game_data(_topic, game_id) | |
| return unless game_id == @uuid | |
| sync_game | |
| publish :game_data_saved, @uuid, :game | |
| end | |
| def sync_game | |
| info 'syncing game' | |
| end | |
| def onconnect | |
| # TODO: react to connection when web connected | |
| # push_state reply: 'connect' | |
| end | |
| def run | |
| # p @uuid | |
| puts 'ok' | |
| end | |
| def start | |
| state = int_state | |
| state.clean_state | |
| players = Actor[:"players_#{@uuid}"] | |
| players.async.build_queue # TODO move to create | |
| if %w(waiting started).map(&:to_sym).include? state.state | |
| state.state = :started | |
| if players.enough_players | |
| publish :game_started, @uuid | |
| start_stage | |
| else | |
| state.state = :terminated | |
| async.terminate_timeout | |
| end | |
| end | |
| end | |
| def start_stage # whats? | |
| players = Actor[:"players_#{@uuid}"] | |
| statements = Actor[:"statements_#{@uuid}"] | |
| Timings::Stage.instance(@uuid).start | |
| statements.clean_current | |
| players.async.push_start_stage | |
| async.start_step | |
| end | |
| def start_step | |
| state = int_state | |
| state.clean_state | |
| players = Actor[:"players_#{@uuid}"] | |
| if %w(s w o t).include? state.stage.to_s | |
| if state.step == 1 | |
| Timings::FirstPitch.instance(@uuid).start | |
| else | |
| Timings::Pitch.instance(@uuid).start | |
| end | |
| state.step_status = state.first_enum(State::STEP_STATUSES) | |
| else | |
| Timings::Ranging.instance(@uuid).start | |
| end | |
| players.async.push_start_step | |
| players.async.push_messages | |
| end | |
| def stage_timeout | |
| state = int_state | |
| players = Actor[:"players_#{@uuid}"] | |
| state.stage = state.next_enum(State::STAGES, state.stage) | |
| Timings::Pitch.instance(@uuid).cancel | |
| Timings::FirstPitch.instance(@uuid).cancel | |
| Timings::BetweenStages.instance(@uuid).start | |
| players.async.push_end_stage | |
| end | |
| def ranging(params = {}) | |
| # value index player | |
| statements = Actor[:"statements_#{@uuid}"] | |
| stage_swot = State::STAGES.fetch(params[:stage], swot: :end)[:swot] | |
| stmnts = statements.visible_for_buf(statements.rebuild_visible_for(stage_swot)) | |
| st = stmnts[params[:index].to_i - 1] | |
| impo = { | |
| player: params[:player], | |
| value: params[:value], | |
| index: params[:index], | |
| stage: stage_swot, | |
| statement: st.value.inspect | |
| } | |
| statements.async.range_for(impo) | |
| publish :importance_added, @uuid, impo | |
| end | |
| def pitch(params = {}) | |
| state = int_state | |
| players = Actor[:"players_#{@uuid}"] | |
| # alarms = Actor[:"alarms_#{@uuid}"] | |
| queue = Actor[:"queue_#{@uuid}"] | |
| return unless state.check_state :pitch, queue.pitcher.uuid | |
| state.set_state :pitch, queue.pitcher.uuid | |
| statements = Actor[:"statements_#{@uuid}"] | |
| tm = Time.now.to_i + (state.setting[:voting_quorum_timeout] || 60) | |
| rpl = (params[:to_replace] || []).map do |r| | |
| if r.is_a? Hash | |
| r[:index].to_i | |
| else | |
| r.to_i | |
| end | |
| end | |
| statement = {value: params[:value], to_replace: rpl, author: queue.pitcher.uuid, stage: state.stage, step: state.step, game_uuid: @uuid} | |
| errors = statements.add statement | |
| if errors.empty? | |
| publish :pitcher_pitch, queue.pitcher.uuid, state.stage | |
| state.step_status = state.next_enum(State::STEP_STATUSES, state.step_status) | |
| Timings::Pitch.instance(@uuid).cancel | |
| Timings::FirstPitch.instance(@uuid).cancel | |
| Timings::VotingQuorum.instance(@uuid).start | |
| publish :statement_pitched, @uuid, statement: statement | |
| players.push_pitch( | |
| errors.merge( | |
| value: params[:value], | |
| to_replace: params[:to_replace] || [], | |
| author: queue.pitcher.uglify_name(state.stage.to_s), | |
| timer: Timings.instance(@uuid).next_stamp | |
| ) | |
| ) | |
| else | |
| state.clean_state | |
| pit = queue.pitcher | |
| pit.publish_msg errors | |
| # unless errors.empty? | |
| # end_step(errors) | |
| # end | |
| end | |
| end | |
| def pitch_timeout(_params = {}) | |
| queue = Actor[:"queue_#{@uuid}"] | |
| state = int_state | |
| pit = queue.pitcher | |
| uuid = pit.uuid | |
| state.set_state :pitch_end, uuid | |
| Timings::Pitch.instance(@uuid).cancel | |
| Timings::FirstPitch.instance(@uuid).cancel | |
| publish :pitch_timeout, @uuid, uuid if pit && pit.alive? | |
| publish :pitcher_timeout, uuid, state.stage | |
| end_step(status: 'timeouted') | |
| end | |
| def pass(_params = {}) | |
| queue = Actor[:"queue_#{@uuid}"] | |
| pit = queue.pitcher | |
| uuid = pit.uuid | |
| state = int_state | |
| return unless state.check_state :pass, uuid | |
| state.set_state :pass, uuid | |
| Timings::Pitch.instance(@uuid).cancel | |
| Timings::FirstPitch.instance(@uuid).cancel | |
| publish :pitcher_passed, uuid, state.stage | |
| publish :pitch_pass, @uuid, queue.pitcher_id | |
| end_step(status: 'passed') | |
| end | |
| def vote(params = {}) | |
| state = int_state | |
| players = Actor[:"players_#{@uuid}"] | |
| # alarms = Actor[:"alarms_#{@uuid}"] | |
| statements = Actor[:"statements_#{@uuid}"] | |
| voting = statements.voting | |
| return unless voting | |
| voting.vote(player: params[:player], result: params[:result]) | |
| publish :vote_added, @uuid, vote: { player: params[:player], result: params[:result] } | |
| if voting.quorum? | |
| if Timings::VotingQuorum.instance(@uuid).next_time.to_f > state.setting[:voting_tail_timeout].to_f | |
| Timings.instance(@uuid).cancel(%w(voting_quorum voting_tail)) | |
| Timings::VotingTail.instance(@uuid).start | |
| players.async.push_quorum | |
| end | |
| end | |
| if voting.voted_count == voting.votable.size | |
| Timings.instance(@uuid).cancel(%w(voting_quorum voting_tail)) | |
| async.end_step(status: voting.calc_result) | |
| end | |
| end | |
| def end_step(params = {}) | |
| state = int_state | |
| players = Actor[:"players_#{@uuid}"] | |
| if %w(rs rw ro rt).include? state.stage.to_s | |
| # msg = {type: 'event', subtype: 'end_step', timer: Timings.instance(@uuid).next_stamp} | |
| # async.publish_msg msg | |
| publish :save_game_data, @uuid | |
| players.async.push_end_step params | |
| async.end_stage | |
| else | |
| queue = Actor[:"queue_#{@uuid}"] | |
| statements = Actor[:"statements_#{@uuid}"] | |
| Timings.instance(@uuid).cancel(%w(pitch first_pitch voting_quorum voting_tail)) | |
| stat = statements.voting | |
| if stat | |
| stat.calc_votes | |
| stat.set_contribution if stat.status == 'accepted' | |
| stat.vote_results! | |
| publish :player_log_push, @uuid, stat.uuid | |
| elsif %w(passed timeouted).include? params[:status] | |
| # не таймаута/пасса нет | |
| queue.pitcher.count_pitcher_score('pass') | |
| end | |
| statements.update_visible | |
| players.async.push_messages | |
| state.step_status = state.next_enum(State::STEP_STATUSES, state.step_status) | |
| state.step_status = state.next_enum(State::STEP_STATUSES, state.step_status) unless state.step_status == :end | |
| Timings::Results.instance(@uuid).start unless %w(passed timeouted).include?(params[:status]) | |
| lg = Actor[:"admin_logger_#{@uuid}"] | |
| queue.next! | |
| lg.step_results :step_results, @uuid | |
| lg.statement_results :statement_results, @uuid, stat.uuid if stat | |
| publish :send_score, @uuid | |
| publish :save_game_data, @uuid | |
| players.async.push_end_step params | |
| async.results_timeout if %w(passed timeouted).include?(params[:status]) | |
| end | |
| end | |
| def voting_quorum_timeout(_params = {}) | |
| # state = int_state | |
| # players = Actor[:"players_#{@uuid}"] | |
| # queue = Actor[:"queue_#{@uuid}"] | |
| statements = Actor[:"statements_#{@uuid}"] | |
| voting = statements.voting | |
| # calc rank results | |
| Timings::VotingQuorum.instance(@uuid).cancel | |
| Timings::VotingTail.instance(@uuid).cancel | |
| return unless voting | |
| publish :vote_timeouts, @uuid, statement: voting.uuid | |
| end_step(status: voting.conclusion) | |
| end | |
| def voting_tail_timeout(_params = {}) | |
| statements = Actor[:"statements_#{@uuid}"] | |
| Timings::VotingQuorum.instance(@uuid).cancel | |
| Timings::VotingTail.instance(@uuid).cancel | |
| voting = statements.voting | |
| return unless voting | |
| publish :vote_timeouts, @uuid, statement: voting.uuid | |
| end_step(status: voting.conclusion) | |
| end | |
| def end_stage(_params = {}) | |
| state = int_state | |
| players = Actor[:"players_#{@uuid}"] | |
| state.stage = state.next_enum(State::STAGES, state.stage) | |
| Timings::Terminate.instance(@uuid).cancel if state.stage == :tr | |
| if %w(sw wo ot tr).include? state.stage.to_s | |
| statements = Actor[:"statements_#{@uuid}"] | |
| Timings::BetweenStages.instance(@uuid).start | |
| players.async.push_end_stage | |
| players.copy_half if state.stage == :sw | |
| players.copy_before if state.stage == :tr | |
| statements.copy_before if state.stage == :tr | |
| if state.stage == :tr | |
| statements.init_importances | |
| publish :ranging, @uuid, stage: state.stage | |
| else | |
| publish :next_stage, @uuid, stage: state.stage | |
| end | |
| elsif %w(rs rw ro rt).include? state.stage.to_s | |
| players.async.push_end_stage | |
| async.start_stage | |
| elsif state.stage == :end | |
| players.async.push_end_stage | |
| Timings::AfterGame.instance(@uuid).start | |
| players.async.push_game_results | |
| async.push_saved_game_results | |
| end | |
| end | |
| def end_game(_params = {}) | |
| info 'TODO end game' | |
| publish :game_done, @uuid | |
| end | |
| def results_timeout(_params = {}) | |
| state = int_state | |
| statements = Actor[:"statements_#{@uuid}"] | |
| Timings::Results.instance(@uuid).cancel | |
| if statements.check_triple_decline | |
| async.end_stage | |
| elsif state.step < state.total_steps | |
| state.step += 1 | |
| lg = Actor[:"admin_logger_#{@uuid}"] | |
| lg.next_pitcher :next_pitcher, @uuid | |
| async.start_step | |
| else | |
| async.end_stage | |
| end | |
| end | |
| def between_stages_timeout(_params = {}) | |
| state = int_state | |
| Timings::BetweenStages.instance(@uuid).cancel | |
| state.stage = state.next_enum(State::STAGES, state.stage) | |
| state.step = 1 | |
| state.step_status = state.first_enum(State::STEP_STATUSES) | |
| async.start_stage | |
| end | |
| def ranging_timeout(_params = {}) | |
| Timings::Ranging.instance(@uuid).cancel | |
| async.end_step | |
| end | |
| # def push_event event, params = {} | |
| # publish_msg({type: 'event', subtype: event}) | |
| # end | |
| def push_state(_params = {}) | |
| # TODO: remove? | |
| end | |
| def push_saved_game_results | |
| sgame = Store::Game.find(uuid: @uuid).first | |
| hsh = { game_id: sgame.mongo_id } | |
| statements = Actor[:"statements_#{@uuid}"] | |
| statements.update_importance_score | |
| statements.rescore | |
| state = int_state | |
| players = Actor[:"players_#{@uuid}"] | |
| stats = %w(s w o t).map(&:to_sym).inject({}) do |rez, sym| | |
| rez[sym] = { statements: [] } | |
| rez[sym][:statements] += statements.visible_for_buf(statements.rebuild_visible_for(sym)).map do |s| | |
| { | |
| author: s.author, | |
| votes: s.votes.map(&:as_json), | |
| importances: s.importances, | |
| result: s.result, | |
| body: s.value, | |
| contribution: s.contribution | |
| } | |
| end | |
| rez | |
| end | |
| vis = %w(s w o t).map(&:to_sym).inject({}) do |rez, sym| | |
| rez[sym] = [] | |
| rez[sym] += statements.visible_for_buf(statements.rebuild_visible_for(sym)).map(&:uuid) | |
| rez | |
| end | |
| statements.statements.each { |stmnt| stmnt.visible = vis[stmnt.stage.to_sym].include?(stmnt.uuid) } | |
| sts = statements.statements.map(&:to_store) | |
| pls = players.players | |
| ps = pls.map do |plyer| | |
| sh_name = plyer.uglify_name(:s) | |
| { | |
| sh_name => { | |
| name: plyer.name, | |
| pitcher_score: plyer.pitcher_score, | |
| pitcher_score_before_ranging: plyer.pitcher_score_before_ranging, | |
| catcher_score_before_ranging: plyer.catcher_score_before_ranging, | |
| uglify_name: sh_name, | |
| pitcher_score_first_half: plyer.pitcher_score_first_half, | |
| catcher_score_first_half: plyer.catcher_score_first_half, | |
| uuid: plyer.uuid, | |
| catcher_score: plyer.catcher_score | |
| } | |
| } | |
| end | |
| hsh.merge!(data: stats, players: ps, statements: sts) | |
| al = Actor[:"admin_logger_#{@uuid}"] | |
| logs = al.as_json | |
| hsh[:logs] = logs.sort_by { |log| log.has_key?(:created_at) ? log[:created_at] : log['created_at'] } | |
| uri = URI(state.setting[:game_results_callback]) | |
| req = Net::HTTP::Post.new uri.request_uri | |
| req.body = "q='#{hsh.to_json}'" | |
| Net::HTTP.start(uri.hostname, uri.port) do |http| | |
| http.request(req) | |
| end | |
| end | |
| def after_game_timeout | |
| async.stop_timers | |
| async.end_game | |
| end | |
| def terminate_timeout | |
| state = int_state | |
| players = Actor[:"players_#{@uuid}"] | |
| state.state = :terminated | |
| publish :game_terminated, @uuid | |
| players.async.push_terminated | |
| async.stop_timers | |
| async.end_game | |
| end | |
| def stop_timers | |
| timings = Timings.instance(@uuid) | |
| timings.async.stop_timers | |
| end | |
| def online! | |
| @online = true | |
| info "#{@uuid} online" | |
| end | |
| def offline! | |
| @online = false | |
| info "#{@uuid} offline" | |
| end | |
| def publish_msg(msg) | |
| return unless @online | |
| ch = Actor[:"gm_chnl_#{@uuid}"] | |
| if ch && ch.alive? | |
| ch.publish_msg msg.to_json | |
| else | |
| offline! | |
| end | |
| end | |
| def finalizer; end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'scores' | |
| # игрок. внутренний класс, привязан к реестру | |
| class Player | |
| include Celluloid | |
| include Celluloid::IO | |
| include Celluloid::Notifications | |
| include Celluloid::Internals::Logger | |
| include Scores | |
| finalizer :finalizer | |
| attr_accessor :name, :email, :channel, :game_uuid, :uuid, :redis, :order, :score, :online, :was_online | |
| attr_accessor :pitcher_score, :pitcher_rank, :catcher_score, :delta | |
| attr_accessor :pitcher_score_first_half, :catcher_score_first_half | |
| attr_accessor :pitcher_score_before_ranging, :catcher_score_before_ranging | |
| def self.build(params = {}) | |
| Store::Player.create( | |
| name: params[:name], | |
| email: params[:email], | |
| state: params[:state], | |
| mongo_id: params[:mongo_id], | |
| uuid: UUID.new.generate, | |
| game_uuid: params[:game_uuid], | |
| order: params[:order] | |
| ) | |
| end | |
| def initialize(params = {}) | |
| @online = false | |
| @was_online = false | |
| # @game_uuid = params[:game_uuid] | |
| if params[:uuid] | |
| store = Store::Player.find(uuid: params[:uuid]).first | |
| warn "player #{params.inspect} invalid" unless store | |
| @uuid = store.uuid | |
| @game_uuid = store.game_uuid | |
| @name = store.name | |
| @email = store.email | |
| @score = store.score | |
| @pitcher_rank = 1.0 | |
| @catcher_score = 0.0 | |
| @delta = 0.0 | |
| end | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| queue.add @uuid | |
| Center.current.async.to_supervise as: "player_logger_#{@uuid}", type: PlayerLogger, args: [{ player_uuid: @uuid }] | |
| subscribe :send_score, :send_players_score | |
| end | |
| def current_stamp | |
| Time.now.to_f.round(3) | |
| end | |
| def send_players_score(_topic, guid) | |
| return unless @game_uuid == guid | |
| players = Actor[:"players_#{@game_uuid}"] | |
| dat_sort = players.players.sort do |sa, sb| | |
| id = sa.uuid | |
| if id == sb.uuid | |
| 0 | |
| elsif id == @uuid | |
| -1 | |
| else | |
| id <=> sb.uuid | |
| end | |
| end | |
| dat = dat_sort.map { |pl| { pitcher: pl.pitcher_rank, catcher: pl.catcher_score } } | |
| msg = { | |
| type: 'event', | |
| subtype: 'ranks', | |
| value: dat | |
| } | |
| publish_msg msg | |
| end | |
| def pitch(params = {}) | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| return unless queue.pitcher_id == @uuid | |
| Timings::Pitch.instance(@game_uuid).cancel | |
| Timings::FirstPitch.instance(@game_uuid).cancel | |
| game = Actor[:"game_#{@game_uuid}"] | |
| game.pitch(params) # TODO: params for game on pitch done (move code to game.pitch) | |
| end | |
| def pass(_params = {}) | |
| # info 'start player.pass' | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| return unless queue.pitcher_id == @uuid | |
| game = Actor[:"game_#{@game_uuid}"] | |
| Timings::Pitch.instance(@game_uuid).cancel | |
| Timings::FirstPitch.instance(@game_uuid).cancel | |
| game.async.pass | |
| end | |
| def count_pitcher_score(typ) | |
| # TODO: step_score.count_pitcher_rank, count_pitcher_score(statements) | |
| state = Actor[:"state_#{@game_uuid}"] | |
| cfg = state.setting | |
| mult = cfg[:"pitcher_rank_multiplier_#{typ}"].to_f | |
| rank = pitcher_rank | |
| rank *= mult | |
| self.pitcher_rank = [rank, cfg[:pitcher_minimum_rank].to_f].max | |
| statements = Actor[:"statements_#{@game_uuid}"] | |
| statements.count_pitchers_score | |
| end | |
| def vote(params = {}) | |
| val = params[:value] | |
| game = Actor[:"game_#{@game_uuid}"] | |
| send_vote(value: val) | |
| game.async.vote(result: val, player: @uuid) | |
| # TODO: params for game on pitch done (move code to game.pitch) | |
| end | |
| def ranging(params = {}) | |
| index = params[:index] | |
| val = params[:val] | |
| game = Actor[:"game_#{@game_uuid}"] | |
| state = Actor[:"state_#{@game_uuid}"] | |
| send_ranging(value: val, index: index) | |
| game.async.ranging(value: val, player: @uuid, index: index, stage: state.stage) | |
| end | |
| def online! | |
| @online = true | |
| @was_online = true | |
| info "#{@uuid} online" | |
| state_obj = Actor[:"state_#{@game_uuid}"] | |
| state = state_obj.state.to_s | |
| players = Actor[:"players_#{@game_uuid}"] | |
| players.check_min_players | |
| async.send_ready reply_to: 'connect' if state == 'waiting' | |
| async.send_state reply_to: 'connect' if state == 'started' | |
| async.send_terminated if state == 'terminated' | |
| async.send_result reply_to: 'connect' unless %w(waiting started).include?(state) | |
| publish :player_online, @game_uuid, uuid: @uuid | |
| end | |
| def offline! | |
| @online = false | |
| players = Actor[:"players_#{@game_uuid}"] | |
| players.check_min_players | |
| info "#{@uuid} offline" | |
| publish :player_offline, @game_uuid, uuid: @uuid | |
| end | |
| def publish_msg(msg) | |
| if @online | |
| ch = Actor[:"chnl_#{@uuid}"] | |
| if ch && ch.alive? | |
| ch.publish_msg msg.merge(time: current_stamp, rel: SWOT_REL, version: SWOT_VERSION).to_json | |
| else | |
| info 'chnl down' | |
| offline! | |
| end | |
| else | |
| info "player #{@uuid} offline" | |
| end | |
| end | |
| def send_result(params = {}) | |
| intervals = params.fetch(:intervals, []).map(&:to_sym) | |
| msg = { type: 'event', | |
| subtype: 'result', | |
| timeout_at: Timings.instance(@game_uuid).stamps(intervals), | |
| time: current_stamp } | |
| publish_msg msg | |
| end | |
| def send_game_results(params = {}) | |
| statements = Actor[:"statements_#{@game_uuid}"] | |
| players = Actor[:"players_#{@game_uuid}"] | |
| stats = %w(s w o t).map(&:to_sym).inject({}) do |r, sym| | |
| r[sym] = { statements: [] } | |
| # TODO: check contrib | |
| r[sym][:statements] += statements.visible_for_buf(statements.rebuild_visible_for(sym)).map do |s| | |
| { body: s.value, contribution: format('%d%', (100.0 * s.contribution_for(@uuid))) } | |
| end | |
| r | |
| end | |
| pls = players.players.sort do |sa, sb| | |
| id = sa.uuid | |
| sbid = sb.uuid | |
| if id == sbid | |
| 0 | |
| elsif id == @uuid | |
| -1 | |
| else | |
| id <=> sbid | |
| end | |
| end | |
| cur = pls.shift | |
| ps = [{ | |
| cur.name => { | |
| pitcher_score: format('%.03f', cur.pitcher_score), | |
| catcher_score: format('%.03f', cur.catcher_score) | |
| } | |
| }] + pls.map do |pl| | |
| { | |
| pl.uglify_name(:s) => { | |
| pitcher_score: format('%.03f', pl.pitcher_score), | |
| catcher_score: format('%.03f', pl.catcher_score) | |
| } | |
| } | |
| end | |
| msg = { type: 'results', value: { data: stats, players: ps } } | |
| publish_msg msg | |
| end | |
| def send_ready(_params = {}) | |
| state = Actor[:"state_#{@game_uuid}"] | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| pit = queue.pitcher.uuid == @uuid | |
| msg = { | |
| type: 'event', subtype: 'ready', name: @name, | |
| start_at: Timings::Start.instance(@game_uuid).at, pitcher: pit, | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(start).map(&:to_sym)), | |
| version: SWOT_VERSION, time: current_stamp, max_steps: state.total_steps | |
| } | |
| publish_msg msg | |
| end | |
| def send_pitch(params = {}) | |
| state = Actor[:"state_#{@game_uuid}"] | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| msg = { | |
| type: 'event', subtype: 'pitched', value: params[:value], to_replace: (params[:to_replace] || []), | |
| author: queue.pitcher.uglify_name(state.stage), | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(stage voting_quorum voting_tail).map(&:to_sym)), | |
| time: current_stamp, step: { status: state.step_status } | |
| } | |
| publish_msg msg | |
| end | |
| def send_pass | |
| msg = { type: 'event', subtype: 'passed', time: current_stamp } | |
| publish_msg msg | |
| end | |
| def send_vote(params = {}) | |
| msg = { | |
| type: 'event', subtype: 'voted', | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(voting_quorum voting_tail stage).map(&:to_sym)), | |
| time: current_stamp | |
| }.merge(params) | |
| publish_msg msg | |
| end | |
| def send_quorum | |
| msg = { | |
| type: 'event', subtype: 'quorum', | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(voting_quorum voting_tail stage).map(&:to_sym)), | |
| continue: true, time: current_stamp | |
| } | |
| publish_msg msg | |
| end | |
| def send_ranging(params = {}) | |
| msg = { | |
| type: 'event', subtype: 'ranging', | |
| timeout_at: Timings.instance(@game_uuid).stamps([:ranging]), | |
| time: current_stamp | |
| }.merge(params) | |
| publish_msg msg | |
| end | |
| def send_messages(_params = {}) | |
| state = Actor[:"state_#{@game_uuid}"] | |
| stage = state.stage.to_s | |
| stage_swot = State::STAGES.fetch(state.stage, swot: :end)[:swot] | |
| statements = Actor[:"statements_#{@game_uuid}"] | |
| stmnts = if %w(rs rw ro rt).include? stage | |
| statements.visible_for_buf(statements.rebuild_visible_for(stage_swot)).map { |st| st.as_json(@uuid) } | |
| elsif %w(s w o t sw wo ot tr).include?(stage) | |
| statements.active_js(@uuid) | |
| else | |
| [] | |
| end | |
| msg = { type: 'event', subtype: 'statements', value: stmnts } | |
| publish_msg msg | |
| end | |
| def send_start_step | |
| state = Actor[:"state_#{@game_uuid}"] | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| msg = { | |
| type: 'event', subtype: 'start_step', turn_in: queue.index(@uuid), | |
| pitcher_name: queue.pitcher.uglify_name(state.stage), | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(ranging stage first_pitch pitch).map(&:to_sym)), | |
| step: { current: state.step, total: state.total_steps, status: state.step_status }, time: current_stamp | |
| } | |
| publish_msg msg | |
| end | |
| def send_end_step(params = {}) | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| statements = Actor[:"statements_#{@game_uuid}"] | |
| stat = statements.last_stat | |
| if stat | |
| quorum = stat.quorum? | |
| status = stat.status | |
| per = 100 * stat.result.to_f | |
| per = 100 - per unless status == 'accepted' | |
| per = quorum ? per.round(1) : 'no_quorum' | |
| rnk = if stat.author == @uuid | |
| { score: @pitcher_rank } | |
| else | |
| { score: @catcher_score, delta: format('%+.1f', @delta) } | |
| end | |
| msg = { | |
| type: 'event', subtype: 'end_step', | |
| result: { | |
| status: (quorum ? status : 'no_quorum'), | |
| players_voted: per | |
| }.merge(rnk), | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(stage results between_stages).map(&:to_sym)), | |
| time: current_stamp | |
| } | |
| else | |
| rnk = if queue.prev_pitcher == @uuid | |
| { rank: @pitcher_rank } | |
| else | |
| { score: @catcher_score, delta: format('%+.1f', @delta) } | |
| end | |
| msg = { | |
| type: 'event', subtype: 'end_step', | |
| result: { | |
| status: params[:status] | |
| }.merge(rnk), | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(stage results between_stages).map(&:to_sym)), | |
| time: current_stamp | |
| } | |
| end | |
| publish_msg msg | |
| end | |
| def send_start_stage | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| state = Actor[:"state_#{@game_uuid}"] | |
| msg = { type: 'event', subtype: 'start_stage', value: state.stage, turn_in: (queue.index(@uuid) || 3) } | |
| publish_msg msg | |
| end | |
| def send_end_stage | |
| state = Actor[:"state_#{@game_uuid}"] | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| msg = { | |
| type: 'event', subtype: 'end_stage', value: state.stage, | |
| pitcher: %w(s w o t sw wo ot).include?(state.stage.to_s) && queue.pitcher == @uuid, | |
| turn_in: (queue.index(@uuid) || 3), player: { turn_in: (queue.index(@uuid) || 3) }, | |
| time: current_stamp, | |
| timeout_at: Timings.instance(@game_uuid).stamps([:between_stages]) | |
| } | |
| publish_msg msg | |
| end | |
| def copy_half | |
| @catcher_score_first_half = @catcher_score | |
| @pitcher_score_first_half = @pitcher_score | |
| end | |
| def copy_before | |
| @catcher_score_before_ranging = @catcher_score | |
| @pitcher_score_before_ranging = @pitcher_score | |
| end | |
| def send_terminated | |
| publish_msg(type: 'event', subtype: 'terminated') | |
| msg = gen_state | |
| publish_msg msg | |
| end | |
| def uglify_name(stage) | |
| %w(s sw t tr).map(&:to_sym).include?(stage) ? "Player #{order}" : name | |
| end | |
| def gen_conclusion | |
| statements = Actor[:"statements_#{@game_uuid}"] | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| state = Actor[:"state_#{@game_uuid}"] | |
| pitcher = queue.pitcher.uglify_name(state.stage) | |
| conclusion = { author: pitcher } | |
| if statements.voting | |
| vot = statements.voting | |
| per = 100 * vot.result.to_f | |
| per = 100 - per unless vot.status == 'accepted' | |
| per = per.round(1) | |
| conclusion.merge!( | |
| value: vot.value, | |
| author: Actor[:"player_#{vot.author}"].uglify_name(state.stage), | |
| to_replace: vot.replaces.map { |r| statements.find(r) }.compact.map(&:value), | |
| status: vot.status, | |
| player_score: 0.0, # TODO: fix scores | |
| players_voted: per | |
| ) | |
| end | |
| conclusion | |
| end | |
| def gen_state(_params = {}) | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| state = Actor[:"state_#{@game_uuid}"] | |
| stage_swot = State::STAGES.fetch(state.stage, swot: :end)[:swot] | |
| statements = Actor[:"statements_#{@game_uuid}"] | |
| if %w(rs rw ro rt).include? state.stage.to_s | |
| stmnts = statements.visible_for_buf(statements.rebuild_visible_for(stage_swot)).map { |s| s.as_json(@uuid) } | |
| elsif %w(s w o t sw wo ot tr).include?(state.stage.to_s) | |
| stmnts = statements.active_js(@uuid) | |
| else | |
| stmnts = [] | |
| end | |
| { | |
| type: 'status', | |
| version: SWOT_VERSION, | |
| state: state.state, | |
| name: @name, | |
| game: { | |
| time: current_stamp, | |
| step: { | |
| current: state.step, | |
| total: state.total_steps, | |
| status: state.step_status | |
| }, | |
| current_stage: state.stage, # one of stages | |
| conclusion: gen_conclusion, | |
| replaces: [], | |
| statements: stmnts, | |
| player: { | |
| turn_in: (queue.index(@uuid) || 3) | |
| }, | |
| started_at: Timings::Start.instance(@game_uuid).at, | |
| timeout_at: Timings.instance(@game_uuid).stamps( | |
| %w(stage pitch first_pitch voting_quorum voting_tail ranging between_stages results start).map(&:to_sym) | |
| ) | |
| } | |
| } | |
| end | |
| def send_state(params = {}) | |
| publish_msg gen_state(params) | |
| end | |
| def catcher_apply_delta(delta) | |
| @catcher_score += delta | |
| @delta = delta | |
| end | |
| def finalizer | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module Message | |
| class Base | |
| attr_accessor :at | |
| include Celluloid::Internals::Logger | |
| def self.try_load(ch, hsh) | |
| new ch, hsh | |
| end | |
| def initialize ch, hash = {} | |
| self.at = ('%10.6f' % Time.now).to_f | |
| end | |
| def process | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require "game" | |
| module Message | |
| class Create < Base | |
| include Celluloid::Internals::Logger | |
| def self.try_load(ch, hsh) | |
| return nil unless hsh[:type] == 'create' | |
| return nil unless ch == '/swot/control' | |
| # return nil unless ch == Control::CONTROL_CHANNEL | |
| Celluloid::Internals::Logger.info "create #{ch.inspect} #{hsh.inspect}" | |
| super | |
| end | |
| def initialize ch, hash = {} | |
| info "init create" | |
| super | |
| @name = hash[:name] if hash[:name] | |
| @players = hash[:players] if hash[:players] && hash[:players].is_a?(Array) | |
| @start = hash[:start] | |
| @set = hash[:settings] | |
| end | |
| def process | |
| info "process create" | |
| super | |
| ::Game.create(name: @name, players: @players, start: @start, reply: 'create', server_setup: Center.current.server_config.dup, settings: @set) | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'hashing' | |
| require 'players/player' | |
| require 'players/queue' | |
| # require 'forwardable' | |
| class Players | |
| include Hashing | |
| # extend Forwardable | |
| include Celluloid | |
| include Celluloid::IO | |
| include Celluloid::Notifications | |
| include Celluloid::Internals::Logger | |
| attr :players | |
| def initialize(params = {}) | |
| @players = [] | |
| guid = params[:game_uuid] | |
| if guid | |
| @game_uuid = guid | |
| queue_wait = future.mk_queue | |
| async.mk_players if queue_wait.value == :ok | |
| end | |
| subscribe :save_game_data, :save_game_data | |
| end | |
| def mk_queue | |
| Center.current.to_supervise as: :"queue_#{@game_uuid}", type: Queue, args: [{ game_uuid: @game_uuid }] | |
| :ok | |
| end | |
| def mk_players | |
| splayers = Store::Player.find(game_uuid: @game_uuid).to_a.sort_by(&:order) | |
| splayers.each do |pl| | |
| p_id = pl.uuid | |
| Center.current.to_supervise(as: :"player_#{p_id}", type: Player, args: [uuid: p_id]) | |
| async.add p_id | |
| end | |
| end | |
| def save_game_data(_topic, game_id) | |
| return unless game_id == @game_uuid | |
| sync_players | |
| publish :game_data_saved, @game_uuid, :players | |
| end | |
| def sync_players | |
| info 'syncing players' | |
| end | |
| def push_event(event, params = {}) | |
| players.each { |pl| pl.send_event event, params } | |
| end | |
| def push_state(params = {}) | |
| players.each { |pl| pl.send_state params } | |
| end | |
| def push_messages(params = {}) | |
| players.each { |pl| pl.send_messages params } | |
| end | |
| def players | |
| @players.map { |id| Actor[:"player_#{id}"] }.select { |pl| pl && pl.alive? } | |
| end | |
| def player_ids | |
| @players | |
| end | |
| def add(player) | |
| ord = players.map { |pl| pl.order.to_i }.inject(0) { |rez, pl| rez >= pl ? rez : pl } | |
| pl_id = player.is_a?(String) ? player : player.uuid | |
| Actor[:"player_#{pl_id}"].order = ord + 1 | |
| @players << pl_id | |
| queue.add pl_id | |
| Control.current.add_player(@game_uuid, pl_id) | |
| end | |
| def copy_half | |
| players.each do |pl| | |
| pl.async.copy_half | |
| end | |
| end | |
| def copy_before | |
| players.each do |pl| | |
| pl.async.copy_before | |
| end | |
| end | |
| def push_start_stage | |
| players.each(&:send_start_stage) | |
| end | |
| def push_start_step | |
| players.each do |pl| | |
| pl.catcher_apply_delta(0.0) | |
| pl.send_start_step | |
| pl.send_state | |
| end | |
| end | |
| def push_end_step(params = {}) | |
| players.each do |pl| | |
| pl.async.send_end_step params | |
| end | |
| end | |
| def push_end_stage | |
| players.each do |pl| | |
| pl.async.send_end_stage | |
| end | |
| end | |
| def push_pitch(params = {}) | |
| players.each do |pl| | |
| pl.send_pitch params | |
| end | |
| end | |
| def push_pass | |
| players.each(&:send_pass) | |
| end | |
| def push_quorum | |
| players.each(&:send_quorum) | |
| end | |
| def push_vote | |
| players.each(&:send_vote) | |
| end | |
| def push_terminated | |
| players.each(&:send_terminated) | |
| end | |
| def push_game_results | |
| players.each(&:send_game_results) | |
| end | |
| def enough_players | |
| state = Actor[:"state_#{@game_uuid}"] | |
| cfg = state.setting | |
| online.size >= cfg[:min_players].to_i | |
| end | |
| def online | |
| players.select(&:online) | |
| end | |
| def was_online | |
| players.select(&:was_online) | |
| end | |
| def check_min_players | |
| state = Actor[:"state_#{@game_uuid}"] | |
| return unless state.state == :started && %w(s sw w wo o ot t).include?(state.stage.to_s) | |
| timer = Timings::Terminate.instance(@game_uuid) | |
| if enough_players && timer.next_time | |
| timer.cancel | |
| else | |
| timer.start | |
| end | |
| end | |
| def find(pl_id) | |
| return Actor[:"player_#{pl_id}"] if @players.include? pl_id | |
| nil | |
| end | |
| def build_queue | |
| queue.rebuild_tail | |
| queue.fill_current | |
| end | |
| def current_pitcher | |
| Actor[:"player_#{queue.first}"] | |
| end | |
| def queue | |
| Actor[:"queue_#{@game_uuid}"] | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'scores' | |
| # игрок. внутренний класс, привязан к реестру | |
| class Player | |
| include Celluloid | |
| include Celluloid::IO | |
| include Celluloid::Notifications | |
| include Celluloid::Internals::Logger | |
| include Scores | |
| finalizer :finalizer | |
| attr_accessor :name, :email, :channel, :game_uuid, :uuid, :redis, :order, :score, :online, :was_online | |
| attr_accessor :pitcher_score, :pitcher_rank, :catcher_score, :delta | |
| attr_accessor :pitcher_score_first_half, :catcher_score_first_half | |
| attr_accessor :pitcher_score_before_ranging, :catcher_score_before_ranging | |
| def self.build(params = {}) | |
| Store::Player.create( | |
| name: params[:name], | |
| email: params[:email], | |
| state: params[:state], | |
| mongo_id: params[:mongo_id], | |
| uuid: UUID.new.generate, | |
| game_uuid: params[:game_uuid], | |
| order: params[:order] | |
| ) | |
| end | |
| def initialize(params = {}) | |
| @online = false | |
| @was_online = false | |
| # @game_uuid = params[:game_uuid] | |
| if params[:uuid] | |
| store = Store::Player.find(uuid: params[:uuid]).first | |
| warn "player #{params.inspect} invalid" unless store | |
| @uuid = store.uuid | |
| @game_uuid = store.game_uuid | |
| @name = store.name | |
| @email = store.email | |
| @score = store.score | |
| @pitcher_rank = 1.0 | |
| @catcher_score = 0.0 | |
| @delta = 0.0 | |
| end | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| queue.add @uuid | |
| Center.current.async.to_supervise as: "player_logger_#{@uuid}", type: PlayerLogger, args: [{ player_uuid: @uuid }] | |
| subscribe :send_score, :send_players_score | |
| end | |
| def current_stamp | |
| Time.now.to_f.round(3) | |
| end | |
| def send_players_score(_topic, guid) | |
| return unless @game_uuid == guid | |
| players = Actor[:"players_#{@game_uuid}"] | |
| dat_sort = players.players.sort do |sa, sb| | |
| id = sa.uuid | |
| if id == sb.uuid | |
| 0 | |
| elsif id == @uuid | |
| -1 | |
| else | |
| id <=> sb.uuid | |
| end | |
| end | |
| dat = dat_sort.map { |pl| { pitcher: pl.pitcher_rank, catcher: pl.catcher_score } } | |
| msg = { | |
| type: 'event', | |
| subtype: 'ranks', | |
| value: dat | |
| } | |
| publish_msg msg | |
| end | |
| def pitch(params = {}) | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| return unless queue.pitcher_id == @uuid | |
| Timings::Pitch.instance(@game_uuid).cancel | |
| Timings::FirstPitch.instance(@game_uuid).cancel | |
| game = Actor[:"game_#{@game_uuid}"] | |
| game.pitch(params) # TODO: params for game on pitch done (move code to game.pitch) | |
| end | |
| def pass(_params = {}) | |
| # info 'start player.pass' | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| return unless queue.pitcher_id == @uuid | |
| game = Actor[:"game_#{@game_uuid}"] | |
| Timings::Pitch.instance(@game_uuid).cancel | |
| Timings::FirstPitch.instance(@game_uuid).cancel | |
| game.async.pass | |
| end | |
| def count_pitcher_score(typ) | |
| # TODO: step_score.count_pitcher_rank, count_pitcher_score(statements) | |
| state = Actor[:"state_#{@game_uuid}"] | |
| cfg = state.setting | |
| mult = cfg[:"pitcher_rank_multiplier_#{typ}"].to_f | |
| rank = pitcher_rank | |
| rank *= mult | |
| self.pitcher_rank = [rank, cfg[:pitcher_minimum_rank].to_f].max | |
| statements = Actor[:"statements_#{@game_uuid}"] | |
| statements.count_pitchers_score | |
| end | |
| def vote(params = {}) | |
| val = params[:value] | |
| game = Actor[:"game_#{@game_uuid}"] | |
| send_vote(value: val) | |
| game.async.vote(result: val, player: @uuid) | |
| # TODO: params for game on pitch done (move code to game.pitch) | |
| end | |
| def ranging(params = {}) | |
| index = params[:index] | |
| val = params[:val] | |
| game = Actor[:"game_#{@game_uuid}"] | |
| state = Actor[:"state_#{@game_uuid}"] | |
| send_ranging(value: val, index: index) | |
| game.async.ranging(value: val, player: @uuid, index: index, stage: state.stage) | |
| end | |
| def online! | |
| @online = true | |
| @was_online = true | |
| info "#{@uuid} online" | |
| state_obj = Actor[:"state_#{@game_uuid}"] | |
| state = state_obj.state.to_s | |
| players = Actor[:"players_#{@game_uuid}"] | |
| players.check_min_players | |
| async.send_ready reply_to: 'connect' if state == 'waiting' | |
| async.send_state reply_to: 'connect' if state == 'started' | |
| async.send_terminated if state == 'terminated' | |
| async.send_result reply_to: 'connect' unless %w(waiting started).include?(state) | |
| publish :player_online, @game_uuid, uuid: @uuid | |
| end | |
| def offline! | |
| @online = false | |
| players = Actor[:"players_#{@game_uuid}"] | |
| players.check_min_players | |
| info "#{@uuid} offline" | |
| publish :player_offline, @game_uuid, uuid: @uuid | |
| end | |
| def publish_msg(msg) | |
| if @online | |
| ch = Actor[:"chnl_#{@uuid}"] | |
| if ch && ch.alive? | |
| ch.publish_msg msg.merge(time: current_stamp, rel: SWOT_REL, version: SWOT_VERSION).to_json | |
| else | |
| info 'chnl down' | |
| offline! | |
| end | |
| else | |
| info "player #{@uuid} offline" | |
| end | |
| end | |
| def send_result(params = {}) | |
| intervals = params.fetch(:intervals, []).map(&:to_sym) | |
| msg = { type: 'event', | |
| subtype: 'result', | |
| timeout_at: Timings.instance(@game_uuid).stamps(intervals), | |
| time: current_stamp } | |
| publish_msg msg | |
| end | |
| def send_game_results(params = {}) | |
| statements = Actor[:"statements_#{@game_uuid}"] | |
| players = Actor[:"players_#{@game_uuid}"] | |
| stats = %w(s w o t).map(&:to_sym).inject({}) do |r, sym| | |
| r[sym] = { statements: [] } | |
| # TODO: check contrib | |
| r[sym][:statements] += statements.visible_for_buf(statements.rebuild_visible_for(sym)).map do |s| | |
| { body: s.value, contribution: format('%d%', (100.0 * s.contribution_for(@uuid))) } | |
| end | |
| r | |
| end | |
| pls = players.players.sort do |sa, sb| | |
| id = sa.uuid | |
| sbid = sb.uuid | |
| if id == sbid | |
| 0 | |
| elsif id == @uuid | |
| -1 | |
| else | |
| id <=> sbid | |
| end | |
| end | |
| cur = pls.shift | |
| ps = [{ | |
| cur.name => { | |
| pitcher_score: format('%.03f', cur.pitcher_score), | |
| catcher_score: format('%.03f', cur.catcher_score) | |
| } | |
| }] + pls.map do |pl| | |
| { | |
| pl.uglify_name(:s) => { | |
| pitcher_score: format('%.03f', pl.pitcher_score), | |
| catcher_score: format('%.03f', pl.catcher_score) | |
| } | |
| } | |
| end | |
| msg = { type: 'results', value: { data: stats, players: ps } } | |
| publish_msg msg | |
| end | |
| def send_ready(_params = {}) | |
| state = Actor[:"state_#{@game_uuid}"] | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| pit = queue.pitcher.uuid == @uuid | |
| msg = { | |
| type: 'event', subtype: 'ready', name: @name, | |
| start_at: Timings::Start.instance(@game_uuid).at, pitcher: pit, | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(start).map(&:to_sym)), | |
| version: SWOT_VERSION, time: current_stamp, max_steps: state.total_steps | |
| } | |
| publish_msg msg | |
| end | |
| def send_pitch(params = {}) | |
| state = Actor[:"state_#{@game_uuid}"] | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| msg = { | |
| type: 'event', subtype: 'pitched', value: params[:value], to_replace: (params[:to_replace] || []), | |
| author: queue.pitcher.uglify_name(state.stage), | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(stage voting_quorum voting_tail).map(&:to_sym)), | |
| time: current_stamp, step: { status: state.step_status } | |
| } | |
| publish_msg msg | |
| end | |
| def send_pass | |
| msg = { type: 'event', subtype: 'passed', time: current_stamp } | |
| publish_msg msg | |
| end | |
| def send_vote(params = {}) | |
| msg = { | |
| type: 'event', subtype: 'voted', | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(voting_quorum voting_tail stage).map(&:to_sym)), | |
| time: current_stamp | |
| }.merge(params) | |
| publish_msg msg | |
| end | |
| def send_quorum | |
| msg = { | |
| type: 'event', subtype: 'quorum', | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(voting_quorum voting_tail stage).map(&:to_sym)), | |
| continue: true, time: current_stamp | |
| } | |
| publish_msg msg | |
| end | |
| def send_ranging(params = {}) | |
| msg = { | |
| type: 'event', subtype: 'ranging', | |
| timeout_at: Timings.instance(@game_uuid).stamps([:ranging]), | |
| time: current_stamp | |
| }.merge(params) | |
| publish_msg msg | |
| end | |
| def send_messages(_params = {}) | |
| state = Actor[:"state_#{@game_uuid}"] | |
| stage = state.stage.to_s | |
| stage_swot = State::STAGES.fetch(state.stage, swot: :end)[:swot] | |
| statements = Actor[:"statements_#{@game_uuid}"] | |
| stmnts = if %w(rs rw ro rt).include? stage | |
| statements.visible_for_buf(statements.rebuild_visible_for(stage_swot)).map { |st| st.as_json(@uuid) } | |
| elsif %w(s w o t sw wo ot tr).include?(stage) | |
| statements.active_js(@uuid) | |
| else | |
| [] | |
| end | |
| msg = { type: 'event', subtype: 'statements', value: stmnts } | |
| publish_msg msg | |
| end | |
| def send_start_step | |
| state = Actor[:"state_#{@game_uuid}"] | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| msg = { | |
| type: 'event', subtype: 'start_step', turn_in: queue.index(@uuid), | |
| pitcher_name: queue.pitcher.uglify_name(state.stage), | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(ranging stage first_pitch pitch).map(&:to_sym)), | |
| step: { current: state.step, total: state.total_steps, status: state.step_status }, time: current_stamp | |
| } | |
| publish_msg msg | |
| end | |
| def send_end_step(params = {}) | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| statements = Actor[:"statements_#{@game_uuid}"] | |
| stat = statements.last_stat | |
| if stat | |
| quorum = stat.quorum? | |
| status = stat.status | |
| per = 100 * stat.result.to_f | |
| per = 100 - per unless status == 'accepted' | |
| per = quorum ? per.round(1) : 'no_quorum' | |
| rnk = if stat.author == @uuid | |
| { score: @pitcher_rank } | |
| else | |
| { score: @catcher_score, delta: format('%+.1f', @delta) } | |
| end | |
| msg = { | |
| type: 'event', subtype: 'end_step', | |
| result: { | |
| status: (quorum ? status : 'no_quorum'), | |
| players_voted: per | |
| }.merge(rnk), | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(stage results between_stages).map(&:to_sym)), | |
| time: current_stamp | |
| } | |
| else | |
| rnk = if queue.prev_pitcher == @uuid | |
| { rank: @pitcher_rank } | |
| else | |
| { score: @catcher_score, delta: format('%+.1f', @delta) } | |
| end | |
| msg = { | |
| type: 'event', subtype: 'end_step', | |
| result: { | |
| status: params[:status] | |
| }.merge(rnk), | |
| timeout_at: Timings.instance(@game_uuid).stamps(%w(stage results between_stages).map(&:to_sym)), | |
| time: current_stamp | |
| } | |
| end | |
| publish_msg msg | |
| end | |
| def send_start_stage | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| state = Actor[:"state_#{@game_uuid}"] | |
| msg = { type: 'event', subtype: 'start_stage', value: state.stage, turn_in: (queue.index(@uuid) || 3) } | |
| publish_msg msg | |
| end | |
| def send_end_stage | |
| state = Actor[:"state_#{@game_uuid}"] | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| msg = { | |
| type: 'event', subtype: 'end_stage', value: state.stage, | |
| pitcher: %w(s w o t sw wo ot).include?(state.stage.to_s) && queue.pitcher == @uuid, | |
| turn_in: (queue.index(@uuid) || 3), player: { turn_in: (queue.index(@uuid) || 3) }, | |
| time: current_stamp, | |
| timeout_at: Timings.instance(@game_uuid).stamps([:between_stages]) | |
| } | |
| publish_msg msg | |
| end | |
| def copy_half | |
| @catcher_score_first_half = @catcher_score | |
| @pitcher_score_first_half = @pitcher_score | |
| end | |
| def copy_before | |
| @catcher_score_before_ranging = @catcher_score | |
| @pitcher_score_before_ranging = @pitcher_score | |
| end | |
| def send_terminated | |
| publish_msg(type: 'event', subtype: 'terminated') | |
| msg = gen_state | |
| publish_msg msg | |
| end | |
| def uglify_name(stage) | |
| %w(s sw t tr).map(&:to_sym).include?(stage) ? "Player #{order}" : name | |
| end | |
| def gen_conclusion | |
| statements = Actor[:"statements_#{@game_uuid}"] | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| state = Actor[:"state_#{@game_uuid}"] | |
| pitcher = queue.pitcher.uglify_name(state.stage) | |
| conclusion = { author: pitcher } | |
| if statements.voting | |
| vot = statements.voting | |
| per = 100 * vot.result.to_f | |
| per = 100 - per unless vot.status == 'accepted' | |
| per = per.round(1) | |
| conclusion.merge!( | |
| value: vot.value, | |
| author: Actor[:"player_#{vot.author}"].uglify_name(state.stage), | |
| to_replace: vot.replaces.map { |r| statements.find(r) }.compact.map(&:value), | |
| status: vot.status, | |
| player_score: 0.0, # TODO: fix scores | |
| players_voted: per | |
| ) | |
| end | |
| conclusion | |
| end | |
| def gen_state(_params = {}) | |
| queue = Actor[:"queue_#{@game_uuid}"] | |
| state = Actor[:"state_#{@game_uuid}"] | |
| stage_swot = State::STAGES.fetch(state.stage, swot: :end)[:swot] | |
| statements = Actor[:"statements_#{@game_uuid}"] | |
| if %w(rs rw ro rt).include? state.stage.to_s | |
| stmnts = statements.visible_for_buf(statements.rebuild_visible_for(stage_swot)).map { |s| s.as_json(@uuid) } | |
| elsif %w(s w o t sw wo ot tr).include?(state.stage.to_s) | |
| stmnts = statements.active_js(@uuid) | |
| else | |
| stmnts = [] | |
| end | |
| { | |
| type: 'status', | |
| version: SWOT_VERSION, | |
| state: state.state, | |
| name: @name, | |
| game: { | |
| time: current_stamp, | |
| step: { | |
| current: state.step, | |
| total: state.total_steps, | |
| status: state.step_status | |
| }, | |
| current_stage: state.stage, # one of stages | |
| conclusion: gen_conclusion, | |
| replaces: [], | |
| statements: stmnts, | |
| player: { | |
| turn_in: (queue.index(@uuid) || 3) | |
| }, | |
| started_at: Timings::Start.instance(@game_uuid).at, | |
| timeout_at: Timings.instance(@game_uuid).stamps( | |
| %w(stage pitch first_pitch voting_quorum voting_tail ranging between_stages results start).map(&:to_sym) | |
| ) | |
| } | |
| } | |
| end | |
| def send_state(params = {}) | |
| publish_msg gen_state(params) | |
| end | |
| def catcher_apply_delta(delta) | |
| @catcher_score += delta | |
| @delta = delta | |
| end | |
| def finalizer | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment