game_okey_table.erl 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500
  1. %%% -------------------------------------------------------------------
  2. %%% Author : Sergii Polkovnikov <serge.polkovnikov@gmail.com>
  3. %%% Description :
  4. %%%
  5. %%% Created : Oct 15, 2012
  6. %%% -------------------------------------------------------------------
  7. -module(game_okey_table).
  8. -behaviour(gen_fsm).
  9. %% --------------------------------------------------------------------
  10. %% Include files
  11. %% --------------------------------------------------------------------
  12. -include_lib("server/include/basic_types.hrl").
  13. -include_lib("server/include/settings.hrl").
  14. -include_lib("server/include/game_okey.hrl").
  15. -include_lib("server/include/game_state.hrl").
  16. -include_lib("server/include/requests.hrl").
  17. %% --------------------------------------------------------------------
  18. %% External exports
  19. -export([start/3,
  20. player_action/3,
  21. parent_message/2,
  22. relay_message/2
  23. ]).
  24. -export([submit/3, signal/3]).
  25. %% gen_fsm callbacks
  26. -export([init/1, handle_event/3, handle_sync_event/4,
  27. handle_info/3, terminate/3, code_change/4]).
  28. -type tash() :: false_okey | {integer(), integer()}.
  29. -record(desk_state,
  30. {
  31. state :: state_take | state_discard | state_finished,
  32. hands :: list({integer(), list(tash())}), %% {SeatNum, Tashes}
  33. discarded :: list({integer(), list(tash())}), %% {SeatNum, Tashes}
  34. deck :: list(tash()),
  35. cur_seat :: integer(),
  36. gosterge :: tash(),
  37. has_gosterge :: undefined | integer(), %% Seat num of a player who has gosterge
  38. have_8_tashes :: list(integer()), %% Seats of players who show 8 tashes combination
  39. finish_reason :: tashes_out | reveal | gosterge_finish, %% Defined only when state = state_finished
  40. finish_info :: term()
  41. }).
  42. -record(player, {?PLAYER}).
  43. -define(STATE_WAITING_FOR_START, state_waiting_for_start).
  44. -define(STATE_PLAYING, state_playing).
  45. -define(STATE_REVEAL_CONFIRMATION, state_reveal_confirmation).
  46. -define(STATE_FINISHED, state_finished).
  47. -define(STATE_PAUSE, state_pause).
  48. -define(STATE_SET_FINISHED, state_set_finished).
  49. -define(HAND_SIZE, 14).
  50. -define(SEATS_NUM, 4).
  51. -define(RELAY, relay_ng).
  52. -define(DESK, game_okey_desk).
  53. -define(SCORING, game_okey_scoring).
  54. %% ====================================================================
  55. %% External functions
  56. %% ====================================================================
  57. start(GameId, TableId, TableParams) ->
  58. gen_fsm:start(?MODULE, [GameId, TableId, TableParams], []).
  59. player_action(Srv, PlayerId, Action) ->
  60. gen_fsm:sync_send_all_state_event(Srv, {player_action, PlayerId, Action}).
  61. parent_message(Srv, Message) ->
  62. gen_fsm:send_all_state_event(Srv, {parent_message, Message}).
  63. relay_message(Srv, Message) ->
  64. gen_fsm:send_all_state_event(Srv, {relay_message, Message}).
  65. submit(Table, PlayerId, Action) ->
  66. player_action(Table, PlayerId, {submit, Action}).
  67. signal(Table, PlayerId, Signal) ->
  68. player_action(Table, PlayerId, {signal, Signal}).
  69. %% ====================================================================
  70. %% Server functions
  71. %% ====================================================================
  72. init([GameId, TableId, Params]) ->
  73. Parent = proplists:get_value(parent, Params),
  74. PlayersInfo = proplists:get_value(players, Params),
  75. TableName = proplists:get_value(table_name, Params),
  76. MultFactor = proplists:get_value(mult_factor, Params),
  77. SlangFlag = proplists:get_value(slang_allowed, Params),
  78. ObserversFlag = proplists:get_value(observers_allowed, Params),
  79. TournamentType = proplists:get_value(tournament_type, Params),
  80. Speed = proplists:get_value(speed, Params),
  81. TurnTimeout = proplists:get_value(turn_timeout, Params, get_timeout(turn, Speed)), %% TODO Set this param explictly
  82. RevConfirmTimeout = proplists:get_value(reveal_confirmation_timeout, Params, get_timeout(challenge, Speed)), %% TODO Set this param explictly
  83. ReadyTimeout = proplists:get_value(ready_timeout, Params, get_timeout(ready, Speed)), %% TODO Set this param explictly
  84. RoundTimeout = proplists:get_value(round_timeout, Params),
  85. SetTimeout = proplists:get_value(set_timeout, Params),
  86. GameMode = proplists:get_value(game_type, Params),
  87. Rounds = proplists:get_value(rounds, Params),
  88. RevealConfirmation = proplists:get_value(reveal_confirmation, Params),
  89. NextSeriesConfirmation = proplists:get_value(next_series_confirmation, Params),
  90. PauseMode = proplists:get_value(pause_mode, Params),
  91. GostergeFinishAllowed = proplists:get_value(gosterge_finish_allowed, Params),
  92. SocialActionsEnabled = proplists:get_value(social_actions_enabled, Params),
  93. TTable = proplists:get_value(ttable, Params),
  94. Tour = proplists:get_value(tour, Params),
  95. Tours = proplists:get_value(tours, Params),
  96. %% Next two options will be passed on table respawn (after fail or service maintaince)
  97. ScoringState = proplists:get_value(scoring_state, Params, init_scoring(GameMode, PlayersInfo, Rounds)),
  98. CurRound = proplists:get_value(cur_round, Params, 0),
  99. Players = init_players(PlayersInfo),
  100. RelayParams = [{players, [{PlayerId, UserInfo#'PlayerInfo'.id} || {PlayerId, UserInfo, _, _} <- PlayersInfo]},
  101. {observers_allowed, true},
  102. {table, {?MODULE, self()}}],
  103. {ok, Relay} = ?RELAY:start(RelayParams),
  104. %% {ok, ObserverPid} = game_observer:mypid(),
  105. %% [begin gas:info(?MODULE,"OKEY_TABLE subscribe observer to player ~p", [PI]),
  106. %% ?RELAY:subscribe(Relay, ObserverPid, PlayerId, observer) end|| {PlayerId, #'PlayerInfo'{robot = false}, _, _} = PI <- PlayersInfo],
  107. gas:info(?MODULE,"OKEY_NG_TABLE_TRN_DBG <~p,~p> Set timeout: ~p, round timeout: ~p.", [GameId, TableId, SetTimeout, RoundTimeout]),
  108. gas:info(?MODULE,"OKEY_NG_TABLE_TRN_DBG <~p,~p> PlayersInfo: ~p.", [GameId, TableId, PlayersInfo]),
  109. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Started.", [GameId, TableId]),
  110. parent_notify_table_created(Parent, TableId, Relay),
  111. {ok, ?STATE_WAITING_FOR_START, #okey_state{game_id = GameId,
  112. table_id = TableId,
  113. table_name = TableName,
  114. parent = Parent,
  115. relay = Relay,
  116. mult_factor = MultFactor,
  117. slang_flag = SlangFlag,
  118. observer_flag = ObserversFlag,
  119. tournament_type = TournamentType,
  120. tour = Tour,
  121. tours = Tours,
  122. speed = Speed,
  123. turn_timeout = TurnTimeout,
  124. reveal_confirmation_timeout = RevConfirmTimeout,
  125. ready_timeout = ReadyTimeout,
  126. round_timeout = RoundTimeout,
  127. set_timeout = SetTimeout,
  128. game_mode = GameMode,
  129. rounds = Rounds,
  130. reveal_confirmation = RevealConfirmation,
  131. next_series_confirmation = NextSeriesConfirmation,
  132. pause_mode = PauseMode,
  133. gosterge_finish_allowed = GostergeFinishAllowed,
  134. social_actions_enabled = SocialActionsEnabled,
  135. players = Players,
  136. start_seat = crypto:rand_uniform(1, ?SEATS_NUM + 1),
  137. cur_round = CurRound,
  138. scoring_state = ScoringState,
  139. tournament_table = TTable
  140. }}.
  141. handle_event({parent_message, Message}, StateName,
  142. #okey_state{game_id = GameId, table_id = TableId} = StateData) ->
  143. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received message from the parent: ~p.",
  144. [GameId, TableId, Message]),
  145. handle_parent_message(Message, StateName, StateData);
  146. handle_event({relay_message, Message}, StateName,
  147. #okey_state{game_id = GameId, table_id = TableId} = StateData) ->
  148. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received message from the relay: ~p.",
  149. [GameId, TableId, Message]),
  150. handle_relay_message(Message, StateName, StateData);
  151. handle_event(_Event, StateName, StateData) ->
  152. {next_state, StateName, StateData}.
  153. handle_sync_event({player_action, PlayerId, Action}, From, StateName,
  154. #okey_state{players = Players} = StateData) ->
  155. case get_player(PlayerId, Players) of
  156. {ok, Player} ->
  157. handle_player_action(Player, Action, From, StateName, StateData);
  158. error ->
  159. {reply, {error, you_are_not_a_player}, StateName, StateData}
  160. end;
  161. handle_sync_event(_Event, _From, StateName, StateData) ->
  162. Reply = ok,
  163. {reply, Reply, StateName, StateData}.
  164. handle_info({timeout, Magic}, ?STATE_PLAYING,
  165. #okey_state{timeout_magic = Magic, game_id = GameId, table_id = TableId} = StateData) ->
  166. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Move timeout. Do an automatic move(s).", [GameId, TableId]),
  167. do_timeout_moves(StateData);
  168. handle_info({round_timeout, Round}, ?STATE_PLAYING,
  169. #okey_state{cur_round = Round, desk_state = DeskState, game_id = GameId,
  170. table_id = TableId, timeout_timer = TRef} = StateData) ->
  171. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Time to finish round ~p because the round timeout.", [GameId, TableId, Round]),
  172. if TRef =/= undefined -> erlang:cancel_timer(TRef);
  173. true -> do_nothing
  174. end,
  175. finalize_round(StateData#okey_state{desk_state = DeskState#desk_state{finish_reason = timeout}});
  176. handle_info(set_timeout, StateName,
  177. #okey_state{cur_round = Round, desk_state = DeskState, game_id = GameId,
  178. table_id = TableId, timeout_timer = TRef} = StateData) when
  179. StateName =/= ?STATE_SET_FINISHED ->
  180. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Time to finish round ~p and the set because the set timeout.", [GameId, TableId, Round]),
  181. if TRef =/= undefined -> erlang:cancel_timer(TRef);
  182. true -> do_nothing
  183. end,
  184. finalize_round(StateData#okey_state{desk_state = DeskState#desk_state{finish_reason = set_timeout}});
  185. handle_info({timeout, Magic}, ?STATE_REVEAL_CONFIRMATION,
  186. #okey_state{timeout_magic = Magic, wait_list = WL, game_id = GameId, table_id = TableId,
  187. reveal_confirmation_list = CList} = StateData) ->
  188. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Time to check reveal confirmation responses.", [GameId, TableId]),
  189. NewCList = lists:foldl(fun(SeatNum, Acc) -> [{SeatNum, false} | Acc] end, CList, WL),
  190. finalize_round(StateData#okey_state{reveal_confirmation_list = NewCList});
  191. handle_info(Info, StateName, #okey_state{game_id = GameId, table_id = TableId} = StateData) ->
  192. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Unexpected message(info) received at state <~p>: ~p.",
  193. [GameId, TableId, StateName, Info]),
  194. {next_state, StateName, StateData}.
  195. terminate(Reason, StateName, #okey_state{game_id = GameId, table_id = TableId, relay = Relay}) ->
  196. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Shutting down at state: <~p>. Reason: ~p",
  197. [GameId, TableId, StateName, Reason]),
  198. relay_stop(Relay),
  199. ok.
  200. code_change(_OldVsn, StateName, StateData, _Extra) ->
  201. {ok, StateName, StateData}.
  202. %% --------------------------------------------------------------------
  203. %%% Internal functions
  204. %% --------------------------------------------------------------------
  205. %% handle_parent_message(Msg, StateName, StateData)
  206. handle_parent_message({register_player, RequestId, UserInfo, PlayerId, SeatNum}, StateName,
  207. #okey_state{table_id = TableId, players = Players,
  208. parent = Parent, relay = Relay} = StateData) ->
  209. #'PlayerInfo'{id = UserId, robot = IsBot} = UserInfo,
  210. NewPlayers = reg_player(PlayerId, SeatNum, UserId, IsBot, UserInfo, _Connected = false, Players),
  211. relay_register_player(Relay, UserId, PlayerId),
  212. %% TODO: Send notificitations to gamesessions (we have no such notification)
  213. parent_confirm_registration(Parent, TableId, RequestId),
  214. {next_state, StateName, StateData#okey_state{players = NewPlayers}};
  215. handle_parent_message({replace_player, RequestId, UserInfo, PlayerId, SeatNum}, StateName,
  216. #okey_state{game_id = GameId, table_id = TableId, players = Players,
  217. parent = Parent, relay = Relay} = StateData) ->
  218. #'PlayerInfo'{id = UserId, robot = IsBot} = UserInfo,
  219. #player{id = OldPlayerId} = get_player_by_seat_num(SeatNum, Players),
  220. NewPlayers = del_player(OldPlayerId, Players),
  221. NewPlayers2 = reg_player(PlayerId, SeatNum, UserId, IsBot, UserInfo, _Connected = false, NewPlayers),
  222. relay_kick_player(Relay, OldPlayerId),
  223. relay_register_player(Relay, UserId, PlayerId),
  224. ReplaceMsg = create_player_left(SeatNum, UserInfo, Players),
  225. relay_publish_ge(Relay, ReplaceMsg, StateData),
  226. parent_confirm_replacement(Parent, TableId, RequestId),
  227. {next_state, StateName, StateData#okey_state{players = NewPlayers2}};
  228. handle_parent_message(start_round, StateName,
  229. #okey_state{game_id = GameId, game_mode = GameMode, cur_round = CurRound,
  230. gosterge_finish_allowed = GostergeFinishAllowed,
  231. start_seat = LastStartSeat, players = Players,
  232. relay = Relay, turn_timeout = TurnTimeout,
  233. round_timeout = RoundTimeout, set_timeout = SetTimeout,
  234. set_timer = SetTRef, scoring_state = ScoringState} = StateData)
  235. when StateName == ?STATE_WAITING_FOR_START;
  236. StateName == ?STATE_FINISHED ->
  237. NewCurRound = CurRound + 1,
  238. StartSeat = next_seat_num(LastStartSeat),
  239. Deck = deck:shuffle(deck:init_deck(okey)),
  240. {Gosterge, Deck1} = choose_gosterge(Deck),
  241. F = fun(SeatNum, AccDeck) ->
  242. Num = if SeatNum==StartSeat -> ?HAND_SIZE + 1; true -> ?HAND_SIZE end,
  243. lists:split(Num, AccDeck)
  244. end,
  245. {Hands, TablePile} = lists:mapfoldl(F, deck:to_list(Deck1), lists:seq(1, ?SEATS_NUM)),
  246. GostFinishList = if GameMode == countdown andalso GostergeFinishAllowed andalso NewCurRound > 1 ->
  247. {_,_,_,Scores} = ?SCORING:last_round_result(ScoringState),
  248. [SeatNum || {SeatNum, 1} <- Scores];
  249. true -> []
  250. end,
  251. Have8TashesEnabled = GameMode == evenodd orelse GameMode == color,
  252. Params = [{hands, Hands},
  253. {deck, TablePile},
  254. {gosterge, Gosterge},
  255. {cur_player, StartSeat},
  256. {gosterge_finish_list, GostFinishList},
  257. {have_8_tashes_enabled, Have8TashesEnabled}],
  258. {ok, Desk} = ?DESK:start(Params),
  259. DeskState = init_desk_state(Desk),
  260. %% Init timers
  261. {Magic, TRef} = start_timer(TurnTimeout),
  262. RoundTRef = if is_integer(RoundTimeout) ->
  263. erlang:send_after(RoundTimeout, self(), {round_timeout, NewCurRound});
  264. true -> undefined
  265. end,
  266. NewSetTRef = if NewCurRound == 1 ->
  267. if is_integer(SetTimeout) -> erlang:send_after(SetTimeout, self(), set_timeout);
  268. true -> undefined
  269. end;
  270. true -> SetTRef
  271. end,
  272. NewStateData = StateData#okey_state{cur_round = NewCurRound,
  273. start_seat = StartSeat,
  274. desk_rule_pid = Desk,
  275. desk_state = DeskState,
  276. timeout_timer = TRef,
  277. timeout_magic = Magic,
  278. round_timer = RoundTRef,
  279. set_timer = NewSetTRef},
  280. %% {ok, ObserverPid} = game_observer:mypid(),
  281. [begin
  282. %% ?RELAY:subscribe(Relay, ObserverPid, PlayerId, observer),
  283. GameInfoMsg = create_okey_game_info(NewStateData),
  284. send_to_client_ge(Relay, PlayerId, GameInfoMsg, NewStateData),
  285. GameStartedMsg = create_okey_game_started(SeatNum, DeskState, NewCurRound, NewStateData),
  286. send_to_client_ge(Relay, PlayerId, GameStartedMsg, NewStateData)
  287. end || #player{id = PlayerId, seat_num = SeatNum} <- find_connected_players(Players)],
  288. CurSeatNum = DeskState#desk_state.cur_seat,
  289. relay_publish_ge(Relay, create_okey_next_turn(CurSeatNum, Players), NewStateData),
  290. {next_state, ?STATE_PLAYING, NewStateData};
  291. handle_parent_message(show_round_result, StateName,
  292. #okey_state{relay = Relay, scoring_state = ScoringState,
  293. game_id = GameId, table_id = TableId} = StateData) ->
  294. {FinishInfo, RoundScore, AchsPoints, TotalScore} = ?SCORING:last_round_result(ScoringState),
  295. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> RoundScore: ~p Total score: ~p.", [GameId, TableId, RoundScore, TotalScore]),
  296. Msg = case FinishInfo of
  297. {win_reveal, Revealer, WrongRejects, _RevealWithColor, _RevealWithOkey, _RevealWithPairs} ->
  298. create_okey_round_ended_reveal(Revealer, true, WrongRejects, RoundScore,
  299. TotalScore, AchsPoints, StateData);
  300. {fail_reveal, Revealer} ->
  301. create_okey_round_ended_reveal(Revealer, false, [], RoundScore,
  302. TotalScore, AchsPoints, StateData);
  303. tashes_out ->
  304. create_okey_round_ended_tashes_out(RoundScore, TotalScore, AchsPoints,
  305. StateData);
  306. timeout ->
  307. create_okey_round_ended_tashes_out(RoundScore, TotalScore, AchsPoints,
  308. StateData);
  309. set_timeout ->
  310. create_okey_round_ended_tashes_out(RoundScore, TotalScore, AchsPoints,
  311. StateData);
  312. {gosterge_finish, Winner} ->
  313. create_okey_round_ended_gosterge_finish(Winner, RoundScore, TotalScore,
  314. AchsPoints, StateData)
  315. end,
  316. relay_publish_ge(Relay, Msg, StateData),
  317. {next_state, StateName, StateData#okey_state{}};
  318. %% Results = [{PlayerId, Position, Score, Status}] Status = winner | loser | eliminated | none
  319. handle_parent_message({show_series_result, Results}, StateName,
  320. #okey_state{game_id = GameId, relay = Relay, players = Players,
  321. next_series_confirmation = Confirm} = StateData) ->
  322. Msg = create_okey_series_ended(Results, Players, Confirm),
  323. relay_publish_ge(Relay, Msg, StateData),
  324. {next_state, StateName, StateData#okey_state{}};
  325. %% Results = [{UserId, Position, Score, Status}] Status = active | eliminated
  326. handle_parent_message({tour_result, TourNum, Results}, StateName,
  327. #okey_state{game_id = GameId, relay = Relay, tournament_table = TTable} = StateData) ->
  328. NewTTable = [{TourNum, Results} | TTable],
  329. Msg = create_okey_tour_result(TourNum, Results),
  330. relay_publish_ge(Relay, Msg, StateData),
  331. {next_state, StateName, StateData#okey_state{tournament_table = NewTTable}};
  332. handle_parent_message({playing_tables_num, Num}, StateName,
  333. #okey_state{relay = Relay} = StateData) ->
  334. %%XXX Msg = create_okey_playing_tables(Num),
  335. %% relay_publish_ge(Relay, Msg),
  336. {next_state, StateName, StateData};
  337. handle_parent_message(rejoin_players, StateName,
  338. #okey_state{game_id = GameId, relay = Relay,
  339. players = Players} = StateData) ->
  340. [relay_unregister_player(Relay, P#player.id, {rejoin, GameId}) || P <- players_to_list(Players)],
  341. {next_state, StateName, StateData#okey_state{players = players_init()}};
  342. handle_parent_message(disconnect_players, StateName,
  343. #okey_state{relay = Relay, players = Players} = StateData) ->
  344. [relay_unregister_player(Relay, P#player.id, game_over) || P <- players_to_list(Players)],
  345. {next_state, StateName, StateData#okey_state{players = players_init()}};
  346. handle_parent_message(stop, _StateName, StateData) ->
  347. {stop, normal, StateData};
  348. handle_parent_message(Message, StateName,
  349. #okey_state{game_id = GameId, table_id = TableId} = StateData) ->
  350. gas:error(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Unexpected parent message received in state <~p>: ~p. State: ~p. Stopping.",
  351. [GameId, TableId, StateName, Message, StateName]),
  352. {stop, unexpected_parent_message, StateData}.
  353. %%===================================================================
  354. %% handle_relay_message(Msg, StateName, StateData)
  355. handle_relay_message({player_connected, PlayerId} = Msg, StateName,
  356. #okey_state{parent = Parent, game_id = GameId,
  357. table_id = TableId, players = Players} = StateData) ->
  358. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received nofitication from the relay: ~p", [GameId, TableId, Msg]),
  359. case get_player(PlayerId, Players) of
  360. {ok, Player} ->
  361. NewPlayers = store_player_rec(Player#player{connected = true}, Players),
  362. parent_send_player_connected(Parent, TableId, PlayerId),
  363. {next_state, StateName, StateData#okey_state{players = NewPlayers}};
  364. error ->
  365. {next_state, StateName, StateData}
  366. end;
  367. handle_relay_message({player_disconnected, PlayerId}, StateName,
  368. #okey_state{parent = Parent, table_id = TableId, players = Players} = StateData) ->
  369. case get_player(PlayerId, Players) of
  370. {ok, Player} ->
  371. NewPlayers = store_player_rec(Player#player{connected = false}, Players),
  372. parent_send_player_disconnected(Parent, TableId, PlayerId),
  373. {next_state, StateName, StateData#okey_state{players = NewPlayers}};
  374. error ->
  375. {next_state, StateName, StateData}
  376. end;
  377. handle_relay_message({subscriber_added, PlayerId, SubscrId} = Msg, StateName,
  378. #okey_state{relay = Relay, game_id = GameId,
  379. table_id = TableId, tournament_table = TTable,
  380. players = Players} = StateData) ->
  381. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received nofitication from the relay: ~p", [GameId, TableId, Msg]),
  382. PlayerIdIsValid = case PlayerId of
  383. observer -> true;
  384. administrator -> true;
  385. _ ->
  386. case get_player(PlayerId, Players) of
  387. {ok, _} -> true;
  388. error -> false
  389. end
  390. end,
  391. if PlayerIdIsValid ->
  392. GI = create_okey_game_info(StateData),
  393. send_to_subscriber_ge(Relay, SubscrId, GI, StateData),
  394. PlState = create_okey_game_player_state(PlayerId, StateName, StateData),
  395. send_to_subscriber_ge(Relay, SubscrId, PlState, StateData),
  396. relay_allow_broadcast_for_player(Relay, PlayerId),
  397. if TTable =/= undefined ->
  398. [send_to_subscriber_ge(Relay, SubscrId, create_okey_tour_result(TurnNum, Results), StateData)
  399. || {TurnNum, Results} <- lists:sort(TTable)];
  400. true -> do_nothing
  401. end;
  402. true -> do_nothing
  403. end,
  404. {next_state, StateName, StateData};
  405. handle_relay_message(Message, StateName, #okey_state{game_id = GameId, table_id = TableId} = StateData) ->
  406. gas:error(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Unknown relay message received in state <~p>: ~p. State: ~p. Stopping.",
  407. [GameId, TableId, StateName, Message]),
  408. {next_state, StateName, StateData}.
  409. %%===================================================================
  410. %% handle_player_action(Player, Msg, StateName, StateData)
  411. handle_player_action(#player{id = PlayerId, seat_num = SeatNum, user_id = UserId},
  412. {submit, #game_action{action = Action, args = Args} = GA}, From,
  413. StateName,
  414. #okey_state{game_id = GameId, table_id = TableId} = StateData) ->
  415. {Keys,Values} = lists:unzip(Args),
  416. ExtAction = list_to_tuple([Action|Values]),
  417. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Player <~p> (~p)~n submit the game action: ~p.",
  418. [GameId, TableId, PlayerId, UserId, ExtAction]),
  419. do_action(SeatNum, ExtAction, From, StateName, StateData);
  420. handle_player_action(#player{id = PlayerId, user_id = UserId},
  421. {signal, {pause_game, _}=Signal}, _From,
  422. StateName,
  423. #okey_state{table_id = TableId, game_id = GameId, timeout_timer = TRef,
  424. pause_mode = PauseMode, relay = Relay} = StateData) ->
  425. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received signal from player <~p> : ~p. PauseMode: ~p",
  426. [GameId, TableId, PlayerId, Signal, PauseMode]),
  427. case PauseMode of
  428. disabled ->
  429. {reply, {error, pause_disabled}, StateName, StateData};
  430. normal ->
  431. if StateName == ?STATE_PLAYING;
  432. StateName == ?STATE_REVEAL_CONFIRMATION ->
  433. Timeout = case erlang:cancel_timer(TRef) of
  434. false -> 0;
  435. T -> T
  436. end,
  437. relay_publish(Relay, create_game_paused_pause(UserId, GameId)),
  438. {reply, 0, ?STATE_PAUSE, StateData#okey_state{paused_statename = StateName,
  439. paused_timeout_value = Timeout,
  440. timeout_magic = undefined}};
  441. true ->
  442. {reply, {error, pause_not_possible}, StateName, StateData}
  443. end
  444. end;
  445. handle_player_action(#player{id = PlayerId, user_id = UserId},
  446. {signal, {resume_game, _}=Signal}, _From,
  447. StateName,
  448. #okey_state{table_id = TableId, game_id = GameId, pause_mode = PauseMode,
  449. relay = Relay, paused_statename = ResumedStateName,
  450. paused_timeout_value = Timeout} = StateData) ->
  451. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received signal from player <~p> : ~p. PauseMode: ~p",
  452. [GameId, TableId, PlayerId, Signal, PauseMode]),
  453. case PauseMode of
  454. disabled ->
  455. {reply, {error, pause_disabled}, StateName, StateData};
  456. normal ->
  457. if StateName == ?STATE_PAUSE ->
  458. relay_publish(Relay, create_game_paused_resume(UserId, GameId)),
  459. {Magic, TRef} = start_timer(Timeout),
  460. {reply, 0, ResumedStateName, StateData#okey_state{timeout_timer = TRef,
  461. timeout_magic = Magic}};
  462. true ->
  463. {reply, {error, game_is_not_paused}, StateName, StateData}
  464. end
  465. end;
  466. handle_player_action(#player{id = PlayerId},
  467. {signal, Signal}, _From, StateName,
  468. #okey_state{table_id = TableId, game_id = GameId} = StateData) ->
  469. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Received signal from player <~p> : ~p. Ignoring.",
  470. [GameId, TableId, PlayerId, Signal]),
  471. {reply, ok, StateName, StateData};
  472. handle_player_action(_Player, _Message, _From, StateName, StateData) ->
  473. {next_state, StateName, StateData}.
  474. %%===================================================================
  475. do_action(SeatNum, #okey_has_gosterge{}, From, ?STATE_PLAYING = StateName, StateData) ->
  476. do_game_action(SeatNum, i_have_gosterge, From, StateName, StateData);
  477. do_action(_SeatNum, #okey_has_gosterge{}, _From, StateName, StateData) ->
  478. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  479. do_action(SeatNum, #okey_i_have_8_tashes{}, From, ?STATE_PLAYING = StateName, StateData) ->
  480. do_game_action(SeatNum, i_have_8_tashes, From, StateName, StateData);
  481. do_action(_SeatNum, #okey_i_have_8_tashes{}, _From, StateName, StateData) ->
  482. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  483. do_action(SeatNum, #okey_i_saw_okey{}, From, ?STATE_PLAYING = StateName, StateData) ->
  484. do_game_action(SeatNum, see_okey, From, StateName, StateData);
  485. do_action(_SeatNum, #okey_i_saw_okey{}, _From, StateName, StateData) ->
  486. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  487. do_action(SeatNum, #okey_take{pile = 0}, From, ?STATE_PLAYING = StateName, StateData) ->
  488. do_game_action(SeatNum, take_from_table, From, StateName, StateData);
  489. do_action(_SeatNum, #okey_take{pile = 0}, _From, StateName, StateData) ->
  490. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  491. do_action(SeatNum, #okey_take{pile = 1}, From, ?STATE_PLAYING = StateName, StateData) ->
  492. do_game_action(SeatNum, take_from_discarded, From, StateName, StateData);
  493. do_action(_SeatNum, #okey_take{pile = 1}, _From, StateName, StateData) ->
  494. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  495. do_action(SeatNum, #okey_discard{tile = ExtTash}, From, ?STATE_PLAYING = StateName, StateData) ->
  496. Tash = ext_to_tash(ExtTash),
  497. do_game_action(SeatNum, {discard, Tash}, From, StateName, StateData);
  498. do_action(_SeatNum, #okey_discard{}, _From, StateName, StateData) ->
  499. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  500. do_action(SeatNum, #okey_reveal{discarded = ExtDiscarded, hand = ExtHand}, From,
  501. ?STATE_PLAYING = StateName, StateData) ->
  502. Discarded = ext_to_tash(ExtDiscarded),
  503. Hand = [[if ExtTash == null -> null;
  504. true -> ext_to_tash(ExtTash)
  505. end || ExtTash <- Row] || Row <- ExtHand],
  506. do_game_action(SeatNum, {reveal, Discarded, Hand}, From, StateName, StateData);
  507. do_action(_SeatNum, #okey_reveal{}, _From, StateName, StateData) ->
  508. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  509. do_action(SeatNum, #okey_challenge{challenge = Challenge}, From,
  510. ?STATE_REVEAL_CONFIRMATION = StateName, #okey_state{reveal_confirmation_list = CList,
  511. wait_list = WL,
  512. timeout_timer = TRef} = StateData) ->
  513. case lists:member(SeatNum, WL) of
  514. true ->
  515. Confirmed = not Challenge,
  516. NewCList = [{SeatNum, Confirmed} | CList],
  517. NewWL = lists:delete(SeatNum, WL),
  518. if NewWL == [] ->
  519. gen_fsm:reply(From, ok),
  520. erlang:cancel_timer(TRef),
  521. finalize_round(StateData#okey_state{timeout_timer = undefined,
  522. reveal_confirmation_list = NewCList});
  523. true ->
  524. {reply, ok, StateName,
  525. StateData#okey_state{reveal_confirmation_list = NewCList,
  526. wait_list = NewWL}}
  527. end;
  528. false ->
  529. {reply, {error, not_your_turn}, StateName, StateData}
  530. end;
  531. do_action(_SeatNum, #okey_challenge{}, _From, StateName, StateData) ->
  532. {reply, {error, message_not_valid_for_a_current_state}, StateName, StateData};
  533. do_action(_SeatNum, #okey_ready{}, _From, StateName, StateData) ->
  534. {reply, ok, StateName, StateData};
  535. do_action(_SeatNum, _UnsupportedAction, _From, StateName, StateData) ->
  536. {reply, {error, unsupported}, StateName, StateData}.
  537. %%===================================================================
  538. do_timeout_moves(#okey_state{desk_rule_pid = Desk, desk_state = DeskState} = StateData) ->
  539. #desk_state{cur_seat = CurSeatNum,
  540. hands = Hands,
  541. state = DeskStateName} = DeskState,
  542. case DeskStateName of
  543. state_take ->
  544. {ok, Events1} = desk_player_action(Desk, CurSeatNum, take_from_table),
  545. [Tash] = [Tash || {taked_from_table, S, Tash} <- Events1, S==CurSeatNum],
  546. {ok, Events2} = desk_player_action(Desk, CurSeatNum, {discard, Tash}),
  547. Events2_1 = [case E of
  548. {tash_discarded, SeatNum, Tash} ->
  549. {tash_discarded_timeout, SeatNum, Tash};
  550. _ -> E
  551. end || E <- Events2],
  552. Events = Events1 ++ [{auto_take_discard, CurSeatNum, Tash}] ++ Events2_1,
  553. process_game_events(Events, StateData);
  554. state_discard ->
  555. {_, [Tash | _]} = lists:keyfind(CurSeatNum, 1, Hands),
  556. {ok, Events1} = desk_player_action(Desk, CurSeatNum, {discard, Tash}),
  557. Events1_1 = [case E of
  558. {tash_discarded, SeatNum, Tash} ->
  559. {tash_discarded_timeout, SeatNum, Tash};
  560. _ -> E
  561. end || E <- Events1],
  562. Events = [{auto_discard, CurSeatNum, Tash} | Events1_1],
  563. process_game_events(Events, StateData)
  564. end.
  565. %%===================================================================
  566. do_game_action(SeatNum, GameAction, From, StateName,
  567. #okey_state{desk_rule_pid = Desk} = StateData) ->
  568. gas:info(?MODULE,"OKEY_NG_TABLE_TRN do_game_action SeatNum: ~p GameAction: ~p", [SeatNum, GameAction]),
  569. case desk_player_action(Desk, SeatNum, GameAction) of
  570. {ok, Events} ->
  571. Response = case GameAction of
  572. i_have_gosterge ->
  573. true;
  574. i_have_8_tashes ->
  575. true;
  576. take_from_table ->
  577. [Tash] = [Tash || {taked_from_table, S, Tash} <- Events, S==SeatNum],
  578. tash_to_ext(Tash);
  579. take_from_discarded ->
  580. [Tash] = [Tash || {taked_from_discarded, S, Tash} <- Events, S==SeatNum],
  581. tash_to_ext(Tash);
  582. _ -> ok
  583. end,
  584. gen_fsm:reply(From, Response),
  585. process_game_events(Events, StateData);
  586. {error, Reason} ->
  587. ExtError = desk_error_to_ext(Reason),
  588. {reply, ExtError, StateName, StateData}
  589. end.
  590. process_game_events(Events, #okey_state{desk_state = DeskState, players = Players,
  591. game_id = GameId, relay = Relay, timeout_timer = OldTRef,
  592. round_timeout = RoundTimeout, round_timer = RoundTRef,
  593. turn_timeout = TurnTimeout} = StateData) ->
  594. NewDeskState = handle_desk_events(Events, DeskState, Players, Relay, StateData), %% Track the desk and send game events to clients
  595. #desk_state{state = DeskStateName} = NewDeskState,
  596. case DeskStateName of
  597. state_finished ->
  598. if is_integer(RoundTimeout) -> erlang:cancel_timer(RoundTRef); true -> do_nothing end,
  599. erlang:cancel_timer(OldTRef),
  600. on_game_finish(StateData#okey_state{desk_state = NewDeskState});
  601. state_take ->
  602. case [E || {next_player, _} = E <- Events] of %% Find a next player event
  603. [] ->
  604. {next_state, ?STATE_PLAYING, StateData#okey_state{desk_state = NewDeskState}};
  605. [_|_] ->
  606. erlang:cancel_timer(OldTRef),
  607. {Magic, TRef} = start_timer(TurnTimeout),
  608. {next_state, ?STATE_PLAYING, StateData#okey_state{desk_state = NewDeskState,
  609. timeout_timer = TRef,
  610. timeout_magic = Magic}}
  611. end;
  612. state_discard ->
  613. {next_state, ?STATE_PLAYING, StateData#okey_state{desk_state = NewDeskState}}
  614. end.
  615. on_game_finish(#okey_state{desk_state = DeskState,
  616. reveal_confirmation = RevealConfirmation,
  617. reveal_confirmation_timeout = Timeout} = StateData) ->
  618. #desk_state{finish_reason = FinishReason,
  619. finish_info = FinishInfo,
  620. hands = Hands} = DeskState,
  621. if FinishReason == reveal andalso RevealConfirmation ->
  622. {Revealer, _Tashes, _Discarded} = FinishInfo,
  623. WL = [SeatNum || {SeatNum, _} <- Hands, SeatNum =/= Revealer],
  624. {Magic, TRef} = start_timer(Timeout),
  625. {next_state, ?STATE_REVEAL_CONFIRMATION,
  626. StateData#okey_state{reveal_confirmation_list = [],
  627. wait_list = WL,
  628. timeout_timer = TRef,
  629. timeout_magic = Magic}};
  630. true ->
  631. finalize_round(StateData)
  632. end.
  633. %%===================================================================
  634. finalize_round(#okey_state{desk_state = #desk_state{finish_reason = FinishReason,
  635. finish_info = FinishInfo,
  636. hands = Hands,
  637. gosterge = Gosterge,
  638. has_gosterge = WhoHasGosterge,
  639. have_8_tashes = Have8Tashes},
  640. scoring_state = ScoringState,
  641. reveal_confirmation = RevealConfirmation,
  642. reveal_confirmation_list = CList,
  643. parent = Parent, players = Players,
  644. game_id = GameId, table_id = TableId} = StateData) ->
  645. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Finalizing the round. Finish reason: ~p. Finish info: ~p.",
  646. [GameId, TableId, FinishReason, FinishInfo]),
  647. FR = case FinishReason of
  648. tashes_out -> tashes_out;
  649. timeout -> timeout;
  650. set_timeout -> set_timeout;
  651. reveal ->
  652. {Revealer, Tashes, Discarded} = FinishInfo,
  653. ConfirmationList = if RevealConfirmation -> CList; true -> [] end,
  654. CListUId = [{SeatNum, get_user_id_by_seat_num(SeatNum, Players), Response}
  655. || {SeatNum, Response} <- ConfirmationList],
  656. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Confirmation list: ~p.", [GameId, TableId, CListUId]),
  657. {reveal, Revealer, Tashes, Discarded, ConfirmationList};
  658. gosterge_finish ->
  659. Winner = FinishInfo,
  660. {gosterge_finish, Winner}
  661. end,
  662. {NewScoringState, GameOver} = ?SCORING:round_finished(ScoringState, FR, Hands, Gosterge,
  663. WhoHasGosterge, Have8Tashes),
  664. {_, RoundScore, _, TotalScore} = ?SCORING:last_round_result(NewScoringState),
  665. RoundScorePl = [{get_player_id_by_seat_num(SeatNum, Players), Points} || {SeatNum, Points} <- RoundScore],
  666. TotalScorePl = [{get_player_id_by_seat_num(SeatNum, Players), Points} || {SeatNum, Points} <- TotalScore],
  667. if GameOver ->
  668. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Set is over.", [GameId, TableId]),
  669. parent_send_game_res(Parent, TableId, NewScoringState, RoundScorePl, TotalScorePl),
  670. {next_state, ?STATE_SET_FINISHED, StateData#okey_state{scoring_state = NewScoringState}};
  671. true ->
  672. gas:info(?MODULE,"OKEY_NG_TABLE_TRN <~p,~p> Round is over.", [GameId, TableId]),
  673. parent_send_round_res(Parent, TableId, NewScoringState, RoundScorePl, TotalScorePl),
  674. {next_state, ?STATE_FINISHED, StateData#okey_state{scoring_state = NewScoringState}}
  675. end.
  676. %% handle_desk_events(Events, DeskState, Players) -> NextStateData
  677. %% Tracks the desk state and sends events to clients
  678. handle_desk_events([], DeskState, _Players, _Relay, _StateData) ->
  679. DeskState;
  680. handle_desk_events([Event | Events], DeskState, Players, Relay, #okey_state{} = StateData) ->
  681. #desk_state{cur_seat = CurSeatNum,
  682. hands = Hands,
  683. discarded = Discarded,
  684. deck = Deck,
  685. have_8_tashes = Have8Tashes} = DeskState,
  686. NewDeskState =
  687. case Event of
  688. {has_gosterge, SeatNum} ->
  689. Msg = create_okey_player_has_gosterge(SeatNum, Players),
  690. relay_publish_ge(Relay, Msg, StateData),
  691. DeskState#desk_state{has_gosterge = SeatNum};
  692. {has_8_tashes, SeatNum, Value} ->
  693. Msg = create_okey_player_has_8_tashes(SeatNum, Value, Players),
  694. relay_publish_ge(Relay, Msg, StateData),
  695. DeskState#desk_state{have_8_tashes = [SeatNum | Have8Tashes]};
  696. {saw_okey, SeatNum} ->
  697. Msg = create_okey_disable_okey(SeatNum, CurSeatNum, Players),
  698. relay_publish_ge(Relay, Msg, StateData),
  699. DeskState;
  700. {taked_from_discarded, SeatNum, Tash} ->
  701. PrevSeatNum = prev_seat_num(SeatNum),
  702. {_, [Tash | NewPile]} = lists:keyfind(PrevSeatNum, 1, Discarded),
  703. Msg = create_okey_tile_taken_discarded(SeatNum, Tash, length(NewPile), Players),
  704. relay_publish_ge(Relay, Msg, StateData),
  705. NewDiskarded = lists:keyreplace(PrevSeatNum, 1, Discarded, {PrevSeatNum, NewPile}),
  706. {_, Hand} = lists:keyfind(SeatNum, 1, Hands),
  707. NewHands = lists:keyreplace(SeatNum, 1, Hands, {SeatNum, [Tash | Hand]}),
  708. DeskState#desk_state{hands = NewHands, discarded = NewDiskarded, state = state_discard};
  709. {taked_from_table, SeatNum, Tash} ->
  710. [Tash | NewDeck] = Deck,
  711. [ send_to_client_ge(Relay, Id,
  712. create_okey_tile_taken_table(CSN, CurSeatNum, Tash, length(NewDeck), Players), StateData)
  713. || #player{id = Id,seat_num = CSN} <- find_connected_players(Players) ],
  714. {_, Hand} = lists:keyfind(SeatNum, 1, Hands),
  715. NewHands = lists:keyreplace(SeatNum, 1, Hands, {SeatNum, [Tash | Hand]}),
  716. DeskState#desk_state{hands = NewHands, deck = NewDeck, state = state_discard};
  717. {tash_discarded, SeatNum, Tash} ->
  718. Msg = create_okey_tile_discarded(SeatNum, Tash, false, Players),
  719. relay_publish_ge(Relay, Msg, StateData),
  720. {_, Hand} = lists:keyfind(SeatNum, 1, Hands),
  721. NewHands = lists:keyreplace(SeatNum, 1, Hands, {SeatNum, lists:delete(Tash, Hand)}),
  722. {_, Pile} = lists:keyfind(SeatNum, 1, Discarded),
  723. NewDiscarded = lists:keyreplace(SeatNum, 1, Discarded, {SeatNum, [Tash | Pile]}),
  724. DeskState#desk_state{hands = NewHands, discarded = NewDiscarded, state = state_take};
  725. {tash_discarded_timeout, SeatNum, Tash} -> %% Injected event
  726. Msg = create_okey_tile_discarded(SeatNum, Tash, true, Players),
  727. relay_publish_ge(Relay, Msg, StateData),
  728. {_, Hand} = lists:keyfind(SeatNum, 1, Hands),
  729. NewHands = lists:keyreplace(SeatNum, 1, Hands, {SeatNum, lists:delete(Tash, Hand)}),
  730. {_, Pile} = lists:keyfind(SeatNum, 1, Discarded),
  731. NewDiscarded = lists:keyreplace(SeatNum, 1, Discarded, {SeatNum, [Tash | Pile]}),
  732. DeskState#desk_state{hands = NewHands, discarded = NewDiscarded, state = state_take};
  733. {auto_take_discard, SeatNum, Tash} -> %% Injected event
  734. #player{id = PlayerId} = get_player_by_seat_num(SeatNum, Players),
  735. Msg = create_okey_turn_timeout(Tash, Tash),
  736. send_to_client_ge(Relay, PlayerId, Msg, StateData),
  737. DeskState;
  738. {auto_discard, SeatNum, Tash} -> %% Injected event
  739. #player{id = PlayerId} = get_player_by_seat_num(SeatNum, Players),
  740. Msg = create_okey_turn_timeout(null, Tash),
  741. send_to_client_ge(Relay, PlayerId, Msg, StateData),
  742. DeskState;
  743. {next_player, SeatNum} ->
  744. Msg = create_okey_next_turn(SeatNum, Players),
  745. relay_publish_ge(Relay, Msg, StateData),
  746. DeskState#desk_state{cur_seat = SeatNum, state = state_take};
  747. no_winner_finish ->
  748. DeskState#desk_state{state = state_finished,
  749. finish_reason = tashes_out};
  750. {reveal, SeatNum, RevealedTashes, DiscardedTash} ->
  751. Msg = create_okey_revealed(SeatNum, DiscardedTash, RevealedTashes, Players),
  752. relay_publish_ge(Relay, Msg, StateData),
  753. DeskState#desk_state{state = state_finished,
  754. finish_reason = reveal,
  755. finish_info = {SeatNum, RevealedTashes, DiscardedTash}};
  756. {gosterge_finish, SeatNum} ->
  757. DeskState#desk_state{state = state_finished,
  758. finish_reason = gosterge_finish,
  759. finish_info = SeatNum}
  760. end,
  761. handle_desk_events(Events, NewDeskState, Players, Relay, StateData).
  762. %%===================================================================
  763. init_scoring(GameType, PlayersInfo, Rounds) ->
  764. SeatsInfo = [{SeatNum, Points} || {_PlayerId, _UserInfo, SeatNum, Points} <- PlayersInfo],
  765. ?SCORING:init(GameType, SeatsInfo, Rounds).
  766. %% start_timer(Timeout) -> {Magic, TRef}
  767. start_timer(Timeout) ->
  768. Magic = make_ref(),
  769. TRef = erlang:send_after(Timeout, self(), {timeout, Magic}),
  770. {Magic, TRef}.
  771. %% players_init() -> players()
  772. players_init() ->
  773. midict:new().
  774. %% reg_player(PlayerId, SeatNum, UserId, IsBot, Players) -> NewPlayers
  775. reg_player(PlayerId, SeatNum, UserId, IsBot, UserInfo, Connected, Players) ->
  776. store_player_rec(#player{id =PlayerId, seat_num = SeatNum, user_id = UserId,
  777. is_bot = IsBot, info = UserInfo, connected = Connected}, Players).
  778. %% reg_player(#player{}, Players) -> NewPlayers
  779. store_player_rec(#player{id =Id, seat_num = SeatNum, user_id = UserId,
  780. is_bot = IsBot, connected = Connected} = Player, Players) ->
  781. Indices = [{seat_num, SeatNum}, {user_id, UserId}, {is_bot, IsBot}, {connected, Connected}],
  782. midict:store(Id, Player, Indices, Players).
  783. %% get_player_id_by_seat_num(SeatNum, Players) -> PlayerId
  784. get_player_id_by_seat_num(SeatNum, Players) ->
  785. [#player{id = PlayerId}] = midict:geti(SeatNum, seat_num, Players),
  786. PlayerId.
  787. %% get_user_id_by_seat_num(SeatNum, Players) -> PlayerId
  788. get_user_id_by_seat_num(SeatNum, Players) ->
  789. [#player{user_id = UserId}] = midict:geti(SeatNum, seat_num, Players),
  790. UserId.
  791. %% fetch_player(PlayerId, Players) -> Player
  792. fetch_player(PlayerId, Players) ->
  793. midict:fetch(PlayerId, Players).
  794. %% get_player(PlayerId, Players) -> {ok, Player} | error
  795. get_player(PlayerId, Players) ->
  796. midict:find(PlayerId, Players).
  797. %% get_player_by_seat_num(SeatNum, Players) -> Player
  798. get_player_by_seat_num(SeatNum, Players) ->
  799. [Player] = midict:geti(SeatNum, seat_num, Players),
  800. Player.
  801. %% find_players_by_seat_num(SeatNum, Players) -> [Player]
  802. find_players_by_seat_num(SeatNum, Players) ->
  803. midict:geti(SeatNum, seat_num, Players).
  804. %% find_connected_players(Players) -> [Player]
  805. find_connected_players(Players) ->
  806. midict:geti(true, connected, Players).
  807. %% del_player(PlayerId, Players) -> NewPlayers
  808. del_player(PlayerId, Players) ->
  809. midict:erase(PlayerId, Players).
  810. %% players_to_list(Players) -> List
  811. players_to_list(Players) ->
  812. midict:all_values(Players).
  813. %% @spec init_players(PlayersInfo) -> Players
  814. %% @end
  815. %% PlayersInfo = [{PlayerId, UserInfo, SeatNum, StartPoints}]
  816. init_players(PlayersInfo) ->
  817. init_players(PlayersInfo, players_init()).
  818. init_players([], Players) ->
  819. Players;
  820. init_players([{PlayerId, UserInfo, SeatNum, _StartPoints} | PlayersInfo], Players) ->
  821. #'PlayerInfo'{id = UserId, robot = IsBot} = UserInfo,
  822. NewPlayers = reg_player(PlayerId, SeatNum, UserId, IsBot, UserInfo, _Connected = false, Players),
  823. init_players(PlayersInfo, NewPlayers).
  824. %%=================================================================
  825. send_to_subscriber_ge(Relay, SubscrId, Msg, #okey_state{game_id = GameId} = _StateData) ->
  826. [Name|List] = tuple_to_list(Msg),
  827. Event = #game_event{game = GameId, event = Name, args = lists:zip(known_records:fields(Name),List) },
  828. gas:info(?MODULE,"SEND SUB ~p",[Event]),
  829. ?RELAY:table_message(Relay, {to_subscriber, SubscrId, Event}).
  830. send_to_client_ge(Relay, PlayerId, Msg, #okey_state{game_id = GameId} = _StateData) ->
  831. [Name|List] = tuple_to_list(Msg),
  832. Event = #game_event{game = GameId, event = Name, args = lists:zip(known_records:fields(Name),List) },
  833. gas:info(?MODULE,"SEND CLIENT ~p",[Event]),
  834. ?RELAY:table_message(Relay, {to_client, PlayerId, Event}).
  835. relay_publish_ge(Relay, Msg, #okey_state{game_id = GameId} = _StateData) ->
  836. [Name|List] = tuple_to_list(Msg),
  837. Event = #game_event{game = GameId, event = Name, args = lists:zip(known_records:fields(Name),List) },
  838. gas:info(?MODULE,"RELAY PUBLISH ~p",[Event]),
  839. relay_publish(Relay, Event).
  840. relay_publish(Relay, Msg) ->
  841. ?RELAY:table_message(Relay, {publish, Msg}).
  842. relay_allow_broadcast_for_player(Relay, PlayerId) ->
  843. ?RELAY:table_message(Relay, {allow_broadcast_for_player, PlayerId}).
  844. relay_register_player(Relay, UserId, PlayerId) ->
  845. ?RELAY:table_request(Relay, {register_player, UserId, PlayerId}).
  846. relay_unregister_player(Relay, PlayerId, Reason) ->
  847. ?RELAY:table_request(Relay, {unregister_player, PlayerId, Reason}).
  848. relay_kick_player(Relay, PlayerId) ->
  849. ?RELAY:table_request(Relay, {kick_player, PlayerId}).
  850. relay_stop(Relay) ->
  851. ?RELAY:table_message(Relay, stop).
  852. parent_confirm_registration({ParentMod, ParentPid}, TableId, RequestId) ->
  853. ParentMod:table_message(ParentPid, TableId, {response, RequestId, ok}).
  854. parent_confirm_replacement({ParentMod, ParentPid}, TableId, RequestId) ->
  855. ParentMod:table_message(ParentPid, TableId, {response, RequestId, ok}).
  856. parent_notify_table_created({ParentMod, ParentPid}, TableId, RelayPid) ->
  857. ParentMod:table_message(ParentPid, TableId, {table_created, {?RELAY, RelayPid}}).
  858. parent_send_round_res({ParentMod, ParentPid}, TableId, ScoringState, RoundScores, TotalScores) ->
  859. ParentMod:table_message(ParentPid, TableId, {round_finished, ScoringState, RoundScores, TotalScores}).
  860. parent_send_game_res({ParentMod, ParentPid}, TableId, ScoringState, RoundScores, TotalScores) ->
  861. ParentMod:table_message(ParentPid, TableId, {game_finished, ScoringState, RoundScores, TotalScores}).
  862. parent_send_player_connected({ParentMod, ParentPid}, TableId, PlayerId) ->
  863. ParentMod:table_message(ParentPid, TableId, {player_connected, PlayerId}).
  864. parent_send_player_disconnected({ParentMod, ParentPid}, TableId, PlayerId) ->
  865. ParentMod:table_message(ParentPid, TableId, {player_disconnected, PlayerId}).
  866. desk_player_action(Desk, SeatNum, Action) ->
  867. ?DESK:player_action(Desk, SeatNum, Action).
  868. %%===================================================================
  869. create_okey_game_info(#okey_state{table_name = TName, mult_factor = MulFactor,
  870. slang_flag = SlangFlag, observer_flag = ObserverFlag,
  871. speed = Speed, turn_timeout = TurnTimeout,
  872. reveal_confirmation_timeout = RevealConfirmationTimeout,
  873. ready_timeout = ReadyTimeout, game_mode = GameMode,
  874. rounds = Rounds1, players = Players, tour = Tour,
  875. tours = Tours, gosterge_finish_allowed = GostergeFinish,
  876. tournament_type = TournamentType, pause_mode = PauseMode,
  877. social_actions_enabled = SocialActionsEnabled,
  878. next_series_confirmation = ConfirmMode}) ->
  879. PInfos = [case find_players_by_seat_num(SeatNum, Players) of
  880. [#player{info = UserInfo}] -> UserInfo;
  881. [] -> null
  882. end || SeatNum <- lists:seq(1, ?SEATS_NUM)],
  883. Timeouts = #'OkeyTimeouts'{speed = Speed,
  884. turn_timeout = TurnTimeout,
  885. challenge_timeout = RevealConfirmationTimeout,
  886. ready_timeout = ReadyTimeout,
  887. rematch_timeout = ?REMATCH_TIMEOUT},
  888. Sets = if Tours == undefined -> null; true -> Tours end,
  889. SetNo = if Tour == undefined -> null; true -> Tour end,
  890. Rounds = if Rounds1 == infinity -> -1; true -> Rounds1 end,
  891. #okey_game_info{table_name = list_to_binary(TName),
  892. players = PInfos,
  893. timeouts = Timeouts,
  894. game_type = GameMode,
  895. finish_with_gosterge = GostergeFinish,
  896. rounds = Rounds,
  897. sets = Sets,
  898. set_no = SetNo,
  899. mul_factor = MulFactor,
  900. slang_flag = SlangFlag,
  901. observer_flag = ObserverFlag,
  902. pause_enabled = PauseMode == normal,
  903. social_actions_enabled = SocialActionsEnabled,
  904. tournament_type = TournamentType,
  905. series_confirmation_mode = list_to_binary(atom_to_list(ConfirmMode))
  906. }.
  907. create_okey_game_player_state(_PlayerId, ?STATE_WAITING_FOR_START,
  908. #okey_state{cur_round = CurRound, scoring_state = ScoringState,
  909. set_timeout = SetTimeout1, set_timer = SetTRef}) ->
  910. Chanak = ?SCORING:chanak(ScoringState),
  911. SetTimeout = if SetTimeout1 == infinity -> null;
  912. true -> calc_timeout_comp(SetTRef, 2000)
  913. end,
  914. #okey_game_player_state{whos_move = null,
  915. game_state = game_initializing,
  916. piles = null,
  917. tiles = null,
  918. gosterge = null,
  919. pile_height = null,
  920. current_round = CurRound,
  921. next_turn_in = 0,
  922. paused = false,
  923. chanak_points = Chanak,
  924. round_timeout = null,
  925. set_timeout = SetTimeout};
  926. create_okey_game_player_state(PlayerId, ?STATE_PLAYING,
  927. #okey_state{timeout_timer = TRef, cur_round = CurRound,
  928. players = Players, desk_state = DeskState,
  929. scoring_state = ScoringState, round_timer = RoundTRef,
  930. round_timeout = RoundTimeout1, set_timer = SetTRef,
  931. set_timeout = SetTimeout1}) ->
  932. #player{seat_num = SeatNum} = fetch_player(PlayerId, Players),
  933. #desk_state{state = DeskStateName,
  934. hands = Hands,
  935. discarded = Discarded,
  936. gosterge = Gosterge,
  937. deck = DeskDeck,
  938. cur_seat = CurSeatNum} = DeskState,
  939. {_, PlayerHand} = lists:keyfind(SeatNum, 1, Hands),
  940. Hand = [tash_to_ext(Tash) || Tash <- PlayerHand],
  941. #player{user_id = CurUserId} = get_player_by_seat_num(CurSeatNum, Players),
  942. Timeout = calc_timeout(TRef),
  943. F = fun(_, N) ->
  944. #player{user_id = UserId} = get_player_by_seat_num(N, Players),
  945. {_, Tahes} = lists:keyfind(N, 1, Discarded),
  946. {{UserId, [tash_to_ext(Tash) || Tash <- Tahes]}, next_seat_num(N)}
  947. end,
  948. {Piles, _} = lists:mapfoldl(F, prev_seat_num(SeatNum), lists:seq(1, ?SEATS_NUM)),
  949. GameState = statename_to_api_string(DeskStateName),
  950. Chanak = ?SCORING:chanak(ScoringState),
  951. RoundTimeout = if RoundTimeout1 == infinity -> null;
  952. true -> calc_timeout_comp(RoundTRef, 2000)
  953. end,
  954. SetTimeout = if SetTimeout1 == infinity -> null;
  955. true -> calc_timeout_comp(SetTRef, 2000)
  956. end,
  957. #okey_game_player_state{whos_move = CurUserId,
  958. game_state = GameState,
  959. piles = Piles,
  960. tiles = Hand,
  961. gosterge = tash_to_ext(Gosterge),
  962. pile_height = length(DeskDeck),
  963. current_round = CurRound,
  964. next_turn_in = Timeout,
  965. paused = false,
  966. chanak_points = Chanak,
  967. round_timeout = RoundTimeout,
  968. set_timeout = SetTimeout};
  969. create_okey_game_player_state(PlayerId, ?STATE_REVEAL_CONFIRMATION,
  970. #okey_state{timeout_timer = TRef, cur_round = CurRound,
  971. players = Players, desk_state = DeskState,
  972. scoring_state = ScoringState,
  973. set_timeout = SetTimeout1, set_timer = SetTRef}) ->
  974. #player{seat_num = SeatNum} = fetch_player(PlayerId, Players),
  975. #desk_state{hands = Hands,
  976. discarded = Discarded,
  977. gosterge = Gosterge,
  978. deck = DeskDeck,
  979. cur_seat = CurSeatNum} = DeskState,
  980. {_, PlayerHand} = lists:keyfind(SeatNum, 1, Hands),
  981. Hand = [tash_to_ext(Tash) || Tash <- PlayerHand],
  982. #player{user_id = CurUserId} = get_player_by_seat_num(CurSeatNum, Players),
  983. Timeout = calc_timeout(TRef),
  984. F = fun(_, N) ->
  985. Pile = case lists:keyfind(N, 1, Discarded) of
  986. {_, []} -> null;
  987. {_, [Tash|_]} -> tash_to_ext(Tash)
  988. end,
  989. {Pile, next_seat_num(N)}
  990. end,
  991. {Piles, _} = lists:mapfoldl(F, prev_seat_num(SeatNum), lists:seq(1, ?SEATS_NUM)),
  992. Chanak = ?SCORING:chanak(ScoringState),
  993. SetTimeout = if SetTimeout1 == infinity -> null;
  994. true -> calc_timeout_comp(SetTRef, 2000)
  995. end,
  996. #okey_game_player_state{whos_move = CurUserId,
  997. game_state = do_okey_challenge,
  998. piles = Piles,
  999. tiles = Hand,
  1000. gosterge = tash_to_ext(Gosterge),
  1001. pile_height = length(DeskDeck),
  1002. current_round = CurRound,
  1003. next_turn_in = Timeout,
  1004. paused = false,
  1005. chanak_points = Chanak,
  1006. round_timeout = null,
  1007. set_timeout = SetTimeout};
  1008. create_okey_game_player_state(_PlayerId, ?STATE_FINISHED,
  1009. #okey_state{cur_round = CurRound, scoring_state = ScoringState,
  1010. set_timeout = SetTimeout1, set_timer = SetTRef}) ->
  1011. Chanak = ?SCORING:chanak(ScoringState),
  1012. SetTimeout = if SetTimeout1 == infinity -> null;
  1013. true -> calc_timeout_comp(SetTRef, 2000)
  1014. end,
  1015. #okey_game_player_state{whos_move = null,
  1016. game_state = game_initializing,
  1017. piles = null,
  1018. tiles = null,
  1019. gosterge = null,
  1020. pile_height = null,
  1021. current_round = CurRound,
  1022. next_turn_in = 0,
  1023. paused = false,
  1024. chanak_points = Chanak,
  1025. round_timeout = null,
  1026. set_timeout = SetTimeout};
  1027. create_okey_game_player_state(PlayerId, ?STATE_PAUSE,
  1028. #okey_state{paused_statename = PausedStateName,
  1029. paused_timeout_value = Timeout
  1030. } = StateData) ->
  1031. Msg = create_okey_game_player_state(PlayerId, PausedStateName, StateData),
  1032. Msg#okey_game_player_state{next_turn_in = Timeout,
  1033. paused = true}.
  1034. create_okey_game_started(SeatNum, DeskState, CurRound,
  1035. #okey_state{scoring_state = ScoringState, round_timeout = RoundTimeout1,
  1036. set_timeout = SetTimeout1, set_timer = SetTRef}) ->
  1037. Chanak = ?SCORING:chanak(ScoringState),
  1038. #desk_state{hands = Hands,
  1039. gosterge = Gosterge,
  1040. deck = DeskDeck} = DeskState,
  1041. {_, PlayerHand} = lists:keyfind(SeatNum, 1, Hands),
  1042. Hand = [tash_to_ext(Tash) || Tash <- PlayerHand],
  1043. RoundTimeout = if RoundTimeout1 == infinity -> null;
  1044. true -> RoundTimeout1 - 2000
  1045. end,
  1046. SetTimeout = if SetTimeout1 == infinity -> null;
  1047. true -> calc_timeout_comp(SetTRef, 2000)
  1048. end,
  1049. #okey_game_started{tiles = Hand,
  1050. gosterge = tash_to_ext(Gosterge),
  1051. pile_height = length(DeskDeck),
  1052. current_round = CurRound,
  1053. current_set = 1, %% XXX Concept of sets is deprecated
  1054. chanak_points = Chanak,
  1055. round_timeout = RoundTimeout,
  1056. set_timeout = SetTimeout}.
  1057. create_okey_next_turn(CurSeat, Players) ->
  1058. #player{user_id = UserId} = get_player_by_seat_num(CurSeat, Players),
  1059. #okey_next_turn{player = UserId}.
  1060. create_player_left(SeatNum, UserInfo, Players) ->
  1061. #player{user_id = OldUserId} = get_player_by_seat_num(SeatNum, Players),
  1062. IsBot = UserInfo#'PlayerInfo'.robot,
  1063. #player_left{player = OldUserId,
  1064. human_replaced = not IsBot, %% XXX WTF?
  1065. bot_replaced = IsBot, %% XXX WTF?
  1066. replacement = UserInfo}.
  1067. create_okey_player_has_gosterge(SeatNum, Players) ->
  1068. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1069. #okey_player_has_gosterge{player = UserId}.
  1070. create_okey_player_has_8_tashes(SeatNum, Value, Players) ->
  1071. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1072. #okey_player_has_8_tashes{player = UserId,
  1073. value = Value}.
  1074. create_okey_disable_okey(SeatNum, CurSeatNum, Players) ->
  1075. #player{user_id = Who} = get_player_by_seat_num(SeatNum, Players),
  1076. #player{user_id = Whom} = get_player_by_seat_num(CurSeatNum, Players),
  1077. #okey_disable_okey{player = Whom,
  1078. who_disabled = Who}.
  1079. create_okey_tile_taken_discarded(SeatNum, Tash, PileHeight, Players) ->
  1080. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1081. #okey_tile_taken{player = UserId,
  1082. pile = 1, %% From discarded tashes of the previous player
  1083. revealed = tash_to_ext(Tash),
  1084. pile_height = PileHeight}.
  1085. create_okey_tile_taken_table(CSN, SeatNum, Tash, PileHeight, Players) ->
  1086. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1087. #okey_tile_taken{player = UserId,
  1088. pile = 0, %% From the deck on the table
  1089. revealed = case CSN == SeatNum of true -> tash_to_ext(Tash); _ -> null end,
  1090. pile_height = PileHeight}.
  1091. create_okey_tile_discarded(SeatNum, Tash, Timeouted, Players) ->
  1092. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1093. #okey_tile_discarded{player = UserId,
  1094. tile = tash_to_ext(Tash),
  1095. timeouted = Timeouted}.
  1096. create_okey_round_ended_reveal(Revealer, RevealerWin, WrongRejects, RoundScore, TotalScore, PlayersAchsPoints,
  1097. #okey_state{players = Players}) ->
  1098. PlResults = [begin
  1099. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1100. WinnerStatus = if SeatNum == Revealer -> RevealerWin;
  1101. true -> not RevealerWin
  1102. end,
  1103. GoodShot = if SeatNum == Revealer -> RevealerWin;
  1104. true -> not lists:member(SeatNum, WrongRejects)
  1105. end,
  1106. {_, Score} = lists:keyfind(SeatNum, 1, TotalScore),
  1107. {_, ScoreDelta} = lists:keyfind(SeatNum, 1, RoundScore),
  1108. Breakdown = [], %% XXX
  1109. #'OkeyGameR'{player_id = UserId,
  1110. disconnected = false,
  1111. winner = WinnerStatus,
  1112. good_shot = GoodShot,
  1113. skill = 0,
  1114. skill_delta = 0,
  1115. score = Score,
  1116. score_delta = ScoreDelta,
  1117. breakdown = Breakdown}
  1118. end || SeatNum <- lists:seq(1, ?SEATS_NUM)],
  1119. Results = #'OkeyGameResults'{game_id = 0,
  1120. start_datetime = 0,
  1121. end_datetime = 0,
  1122. results = PlResults,
  1123. series_results = []},
  1124. #okey_round_ended{good_shot = RevealerWin,
  1125. reason = <<"reveal">>,
  1126. results = Results,
  1127. next_action = next_round}. %%XXX
  1128. create_okey_round_ended_tashes_out(RoundScore, TotalScore, PlayersAchsPoints,
  1129. #okey_state{players = Players}) ->
  1130. PlResults = [begin
  1131. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1132. {_, Score} = lists:keyfind(SeatNum, 1, TotalScore),
  1133. {_, ScoreDelta} = lists:keyfind(SeatNum, 1, RoundScore),
  1134. Breakdown = [], %% XXX
  1135. #'OkeyGameR'{player_id = UserId,
  1136. disconnected = false,
  1137. winner = none,
  1138. good_shot = false,
  1139. skill = 0,
  1140. skill_delta = 0,
  1141. score = Score,
  1142. score_delta = ScoreDelta,
  1143. breakdown = Breakdown}
  1144. end || SeatNum <- lists:seq(1, ?SEATS_NUM)],
  1145. Results = #'OkeyGameResults'{game_id = 0,
  1146. start_datetime = 0,
  1147. end_datetime = 0,
  1148. results = PlResults,
  1149. series_results = []},
  1150. #okey_round_ended{good_shot = false,
  1151. reason = <<"draw">>,
  1152. results = Results,
  1153. next_action = next_round}. %%XXX
  1154. create_okey_round_ended_gosterge_finish(Winner, RoundScore, TotalScore, PlayersAchsPoints,
  1155. #okey_state{players = Players}) ->
  1156. PlResults = [begin
  1157. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1158. WinnerStatus = SeatNum == Winner,
  1159. {_, Score} = lists:keyfind(SeatNum, 1, TotalScore),
  1160. {_, ScoreDelta} = lists:keyfind(SeatNum, 1, RoundScore),
  1161. Breakdown = [], %% XXX
  1162. #'OkeyGameR'{player_id = UserId,
  1163. disconnected = false,
  1164. winner = WinnerStatus,
  1165. good_shot = WinnerStatus,
  1166. skill = 0,
  1167. skill_delta = 0,
  1168. score = Score,
  1169. score_delta = ScoreDelta,
  1170. breakdown = Breakdown}
  1171. end || SeatNum <- lists:seq(1, ?SEATS_NUM)],
  1172. Results = #'OkeyGameResults'{game_id = 0,
  1173. start_datetime = 0,
  1174. end_datetime = 0,
  1175. results = PlResults,
  1176. series_results = []},
  1177. #okey_round_ended{good_shot = false,
  1178. reason = <<"gosterge_instant">>,
  1179. results = Results,
  1180. next_action = next_round}. %%XXX
  1181. create_okey_series_ended(Results, Players, Confirm) ->
  1182. Standings = [begin
  1183. #player{user_id = UserId} = fetch_player(PlayerId, Players),
  1184. Winner = case Status of %% TODO: Implement in the client support of all statuses
  1185. winner -> <<"true">>;
  1186. _ -> <<"none">>
  1187. end,
  1188. #'OkeySeriesResult'{player_id = UserId, place = Position, score = Score,
  1189. winner = Winner}
  1190. end || {PlayerId, Position, Score, Status} <- Results],
  1191. DialogType = if Confirm -> yes_no;
  1192. true -> ok
  1193. end,
  1194. #okey_series_ended{standings = Standings,
  1195. dialog_type = DialogType}.
  1196. create_okey_tour_result(TurnNum, Results) ->
  1197. Records = [begin
  1198. #okey_turn_record{player_id = UserId, place = Position, score = Score,
  1199. status = Status}
  1200. end || {UserId, Position, Score, Status} <- Results],
  1201. #okey_turn_result{turn_num = TurnNum,
  1202. records = Records}.
  1203. create_okey_revealed(SeatNum, DiscardedTash, TashPlaces, Players) ->
  1204. #player{user_id = UserId} = get_player_by_seat_num(SeatNum, Players),
  1205. TashPlacesExt = [[case T of
  1206. null -> null;
  1207. _ -> tash_to_ext(T)
  1208. end || T <- Row ] || Row <- TashPlaces],
  1209. #okey_revealed{player = UserId, %% FIXME: We need reveal message without confirmation
  1210. discarded = tash_to_ext(DiscardedTash),
  1211. hand = TashPlacesExt}.
  1212. create_okey_turn_timeout(null, TashDiscarded) ->
  1213. #okey_turn_timeout{tile_taken = null,
  1214. tile_discarded = tash_to_ext(TashDiscarded)};
  1215. create_okey_turn_timeout(TashTaken, TashDiscarded) ->
  1216. #okey_turn_timeout{tile_taken = tash_to_ext(TashTaken),
  1217. tile_discarded = tash_to_ext(TashDiscarded)}.
  1218. create_game_paused_pause(UserId, GameId) ->
  1219. #game_paused{game = GameId,
  1220. who = UserId,
  1221. action = pause,
  1222. retries = 0}.
  1223. create_game_paused_resume(UserId, GameId) ->
  1224. #game_paused{game = GameId,
  1225. who = UserId,
  1226. action = resume,
  1227. retries = 0}.
  1228. create_okey_playing_tables(Num) ->
  1229. #okey_playing_tables{num = Num}.
  1230. tash_to_ext(false_okey) -> #'OkeyPiece'{color = 1, value = 0};
  1231. tash_to_ext({Color, Value}) -> #'OkeyPiece'{color = Color, value = Value}.
  1232. ext_to_tash(#'OkeyPiece'{color = 1, value = 0}) -> false_okey;
  1233. ext_to_tash(#'OkeyPiece'{color = Color, value = Value}) -> {Color, Value}.
  1234. %statename_to_api_string(state_wait) -> do_okey_ready;
  1235. statename_to_api_string(state_take) -> do_okey_take;
  1236. statename_to_api_string(state_discard) -> do_okey_discard;
  1237. statename_to_api_string(state_finished) -> game_finished.
  1238. desk_error_to_ext(action_disabled) -> false;
  1239. desk_error_to_ext(no_gosterge) -> false;
  1240. desk_error_to_ext(no_8_tashes) -> false;
  1241. desk_error_to_ext(no_okey_discarded) -> {error, there_is_no_okey_there};
  1242. desk_error_to_ext(not_your_order) -> {error, not_your_turn};
  1243. desk_error_to_ext(blocked) -> {error, okey_is_blocked};
  1244. desk_error_to_ext(no_tash) -> {error, no_tash};
  1245. desk_error_to_ext(no_such_tash) -> {error, no_such_tash};
  1246. desk_error_to_ext(hand_not_match) -> {error, discarded_hand_does_not_match_server_state};
  1247. desk_error_to_ext(E) -> {error, E}.
  1248. %%===================================================================
  1249. get_timeout(turn, fast) -> {ok, Val} = kvs:get(config,"games/okey/turn_timeout_fast", 15000), Val;
  1250. get_timeout(turn, normal) -> {ok, Val} = kvs:get(config,"games/okey/turn_timeout_normal", 30000), Val;
  1251. get_timeout(turn, slow) -> {ok, Val} = kvs:get(config,"games/okey/turn_timeout_slow", 60000), Val;
  1252. get_timeout(challenge, fast) -> {ok, Val} = kvs:get(config,"games/okey/challenge_timeout_fast", 5000), Val;
  1253. get_timeout(challenge, normal) -> {ok, Val} = kvs:get(config,"games/okey/challenge_timeout_normal", 10000), Val;
  1254. get_timeout(challenge, slow) -> {ok, Val} = kvs:get(config,"games/okey/challenge_timeout_slow", 20000), Val;
  1255. get_timeout(ready, fast) -> {ok, Val} = kvs:get(config,"games/okey/ready_timeout_fast", 15000), Val;
  1256. get_timeout(ready, normal) -> {ok, Val} = kvs:get(config,"games/okey/ready_timeout_normal", 25000), Val;
  1257. get_timeout(ready, slow) -> {ok, Val} = kvs:get(config,"games/okey/ready_timeout_slow", 45000), Val.
  1258. %%===================================================================
  1259. calc_timeout(undefined) -> 0;
  1260. calc_timeout(TRef) ->
  1261. case erlang:read_timer(TRef) of
  1262. false -> 0;
  1263. Timeout -> Timeout
  1264. end.
  1265. calc_timeout_comp(TRef, Compensation) ->
  1266. if TRef == undefined -> null;
  1267. true -> case erlang:read_timer(TRef) of
  1268. false -> 0;
  1269. T when T < Compensation -> 0; %% Latency time compensation
  1270. T -> T - Compensation
  1271. end
  1272. end.
  1273. %%===================================================================
  1274. next_seat_num(?SEATS_NUM) -> 1;
  1275. next_seat_num(N) -> N + 1.
  1276. prev_seat_num(1) -> ?SEATS_NUM;
  1277. prev_seat_num(N) -> N - 1.
  1278. %%===================================================================
  1279. init_desk_state(Desk) ->
  1280. SeatsNums = ?DESK:get_seats_nums(Desk),
  1281. Hands = [{SeatNum, ?DESK:get_hand(Desk, SeatNum)} || SeatNum <- SeatsNums],
  1282. Discarded = [{SeatNum, ?DESK:get_discarded(Desk, SeatNum)} || SeatNum <- SeatsNums],
  1283. #desk_state{state = ?DESK:get_state_name(Desk),
  1284. hands = Hands,
  1285. discarded = Discarded,
  1286. deck = ?DESK:get_deck(Desk),
  1287. cur_seat = ?DESK:get_cur_seat(Desk),
  1288. gosterge = ?DESK:get_gosterge(Desk),
  1289. have_8_tashes = ?DESK:get_have_8_tashes(Desk),
  1290. has_gosterge = ?DESK:get_has_gosterge(Desk)}.
  1291. %%===================================================================
  1292. choose_gosterge(Deck) ->
  1293. Pos = crypto:rand_uniform(1, deck:size(Deck)),
  1294. case deck:get(Pos, Deck) of
  1295. {false_okey, _} -> choose_gosterge(Deck);
  1296. {Gosterge, Deck1} -> {Gosterge, Deck1}
  1297. end.