game_okey_table.erl 72 KB

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