okey_desk.erl 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. %%% -------------------------------------------------------------------
  2. %%% Author : Sergei Polkovnikov <serge.polkovnikov@gmail.com>
  3. %%% Description : The desk-level logic for okey game.
  4. %%%
  5. %%% Created : Oct 8, 2012
  6. %%% -------------------------------------------------------------------
  7. %% Parameters:
  8. %% hands - contains tashes of players. A position of an element in the list
  9. %% is a seat number of a player. Each element contain a list of tashes.
  10. %% One of the element must contain 15 tashes and all other elements must
  11. %% contain 14 tashes per each.
  12. %% Type: [Hand1, Hand2, Hand3, Hand4],
  13. %% Hand1 = Hand2 = Hand3 = Hand4 = [tash()]
  14. %% deck - contains tashes which will be placed on the table.
  15. %% Type: [tash()]
  16. %% gosterge - a tash that indicate a jocker. It must not be "fasle okey" tash.
  17. %% Type: tash()
  18. %% cur_player - a seat number of a player who will make first move. The hand
  19. %% of the player must contain 15 tashes.
  20. %% Type: 1 | 2 | 3 | 4
  21. %% gosterge_finish_list - the list of players who allowed to do gosterge finish.
  22. %% Type: [SeatNum]
  23. %% SeatNum = 1 | 2 | 3 | 4
  24. %% have_8_tashes_enabled - if true then users are allowed to show 8 tashes combination
  25. %% Type: boolean()
  26. %% tash() = {Color, Value} | false_okey
  27. %% Color = 1 - 4
  28. %% Value = 1 - 13
  29. %% Players actions: || Errors:
  30. %% i_have_gosterge || action_disabled, no_gosterge
  31. %% i_have_8_tashes || action_disabled, no_8_tashes
  32. %% see_okey || no_okey_discarded
  33. %% take_from_discarded || not_your_order, blocked, no_tash
  34. %% take_from_table || not_your_order, no_tash
  35. %% {discard, Tash} || not_your_order, no_such_tash
  36. %% {reveal, Tash, TashPlaces} || not_your_order, no_such_tash, hand_not_match
  37. %% Outgoing events:
  38. %% {has_gosterge, SeatNum}
  39. %% {has_8_tashes, SeatNum, Value}
  40. %% {saw_okey, SeatNum}
  41. %% {taked_from_discarded, SeatNum, Tash}
  42. %% {taked_from_table, SeatNum, Tash}
  43. %% {tash_discarded, SeatNum, Tash}
  44. %% {next_player, SeatNum}
  45. %% no_winner_finish
  46. %% {reveal, SeatNum, TashPlaces, DicardedTash}
  47. %% {gosterge_finish, SeatNum}
  48. -module(okey_desk).
  49. -behaviour(gen_fsm).
  50. %% --------------------------------------------------------------------
  51. %% Include files
  52. %% --------------------------------------------------------------------
  53. %% --------------------------------------------------------------------
  54. %% External exports
  55. -export([
  56. start/1,
  57. stop/1,
  58. player_action/3
  59. ]).
  60. -export([
  61. get_state_name/1,
  62. get_seats_nums/1,
  63. get_cur_seat/1,
  64. get_gosterge/1,
  65. get_deck/1,
  66. get_hand/2,
  67. get_discarded/2,
  68. get_have_8_tashes/1,
  69. get_has_gosterge/1
  70. ]).
  71. %% gen_fsm callbacks
  72. -export([init/1, handle_event/3,
  73. handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
  74. -type tash() :: false_okey | {integer(), integer()}.
  75. -record(player,
  76. {
  77. id :: integer(),
  78. hand :: deck:deck(),
  79. can_show_gosterge :: boolean(),
  80. can_show_8_tashes :: boolean(),
  81. finished_by_okey :: boolean(),
  82. discarded :: deck:deck()
  83. }).
  84. -record(state,
  85. {
  86. gosterge_finish_list :: list(integer()), %% Seats nums of players which allowed to do gosterge finish
  87. have_8_tashes_enabled :: boolean(),
  88. players :: list(#player{}),
  89. cur_player :: integer(),
  90. deck :: deck:deck(),
  91. gosterge :: tash(),
  92. okey :: tash(),
  93. okey_blocked :: boolean(),
  94. has_gosterge :: undefined | integer(), %% Seat num of a player who has gosterge
  95. have_8_tashes_list :: list(integer()) %% Seats nums of players who collected 8 tashes combination
  96. }).
  97. -define(STATE_TAKE, state_take).
  98. -define(STATE_DISCARD, state_discard).
  99. -define(STATE_FINISHED, state_finished).
  100. %% ====================================================================
  101. %% External functions
  102. %% ====================================================================
  103. start(Params) -> gen_fsm:start(?MODULE, Params, []).
  104. player_action(Desk, SeatNum, Action) ->
  105. gen_fsm:sync_send_all_state_event(Desk, {player_action, SeatNum, Action}).
  106. stop(Desk) -> gen_fsm:send_all_state_event(Desk, stop).
  107. get_state_name(Desk) -> gen_fsm:sync_send_all_state_event(Desk, get_state_name).
  108. get_cur_seat(Desk) -> gen_fsm:sync_send_all_state_event(Desk, get_cur_seat).
  109. get_seats_nums(Desk) -> gen_fsm:sync_send_all_state_event(Desk, get_seats_nums).
  110. get_gosterge(Desk) -> gen_fsm:sync_send_all_state_event(Desk, get_gosterge).
  111. get_deck(Desk) -> gen_fsm:sync_send_all_state_event(Desk, get_deck).
  112. get_hand(Desk, SeatNum) -> gen_fsm:sync_send_all_state_event(Desk, {get_hand, SeatNum}).
  113. get_discarded(Desk, SeatNum) -> gen_fsm:sync_send_all_state_event(Desk, {get_discarded, SeatNum}).
  114. get_have_8_tashes(Desk) -> gen_fsm:sync_send_all_state_event(Desk, get_have_8_tashes).
  115. get_has_gosterge(Desk) -> gen_fsm:sync_send_all_state_event(Desk, get_has_gosterge).
  116. %% ====================================================================
  117. %% Server functions
  118. %% ====================================================================
  119. %% --------------------------------------------------------------------
  120. init(Params) ->
  121. Hands = get_param(hands, Params),
  122. Deck = get_param(deck, Params),
  123. Gosterge = get_param(gosterge, Params),
  124. CurPlayer = get_param(cur_player, Params),
  125. GostFinishList = get_param(gosterge_finish_list, Params),
  126. Have8TashesEnabled = get_param(have_8_tashes_enabled, Params),
  127. validate_params(Hands, Deck, Gosterge, CurPlayer, GostFinishList, Have8TashesEnabled),
  128. Players = init_players(Hands),
  129. Okey = gosterge_to_okey(Gosterge),
  130. {ok, ?STATE_DISCARD, #state{players = Players,
  131. gosterge_finish_list = GostFinishList,
  132. have_8_tashes_enabled = Have8TashesEnabled,
  133. deck = deck:from_list(Deck),
  134. gosterge = Gosterge,
  135. okey = Okey,
  136. cur_player = CurPlayer,
  137. okey_blocked = false,
  138. has_gosterge = undefined,
  139. have_8_tashes_list = []}}.
  140. %% --------------------------------------------------------------------
  141. handle_event(stop, _StateName, StateData) ->
  142. {stop, normal, StateData};
  143. handle_event(_Event, StateName, StateData) ->
  144. {next_state, StateName, StateData}.
  145. %% --------------------------------------------------------------------
  146. handle_sync_event({player_action, SeatNum, Action}, _From, StateName, StateData) ->
  147. case handle_player_action(SeatNum, Action, StateName, StateData) of
  148. {ok, Events, NewStateName, NewStateData} ->
  149. {reply, {ok, lists:reverse(Events)}, NewStateName, NewStateData};
  150. {error, Reason} ->
  151. {reply, {error, Reason}, StateName, StateData}
  152. end;
  153. handle_sync_event(get_state_name, _From, StateName,
  154. StateData) ->
  155. {reply, StateName, StateName, StateData};
  156. handle_sync_event(get_cur_seat, _From, StateName,
  157. #state{cur_player = CurSeat} = StateData) ->
  158. {reply, CurSeat, StateName, StateData};
  159. handle_sync_event(get_seats_nums, _From, StateName,
  160. #state{players = Players} = StateData) ->
  161. SeatsNums = [P#player.id || P <- Players],
  162. {reply, SeatsNums, StateName, StateData};
  163. handle_sync_event(get_gosterge, _From, StateName,
  164. #state{gosterge = Gosterge} = StateData) ->
  165. {reply, Gosterge, StateName, StateData};
  166. handle_sync_event(get_deck, _From, StateName,
  167. #state{deck = Deck} = StateData) ->
  168. {reply, deck:to_list(Deck), StateName, StateData};
  169. handle_sync_event({get_hand, SeatNum}, _From, StateName,
  170. #state{players = Players} = StateData) ->
  171. #player{hand = Hand} = get_player(SeatNum, Players),
  172. {reply, deck:to_list(Hand), StateName, StateData};
  173. handle_sync_event({get_discarded, SeatNum}, _From, StateName,
  174. #state{players = Players} = StateData) ->
  175. #player{discarded = Discarded} = get_player(SeatNum, Players),
  176. {reply, deck:to_list(Discarded), StateName, StateData};
  177. handle_sync_event(get_have_8_tashes, _From, StateName,
  178. #state{have_8_tashes_list = Have8TashesList} = StateData) ->
  179. {reply, Have8TashesList, StateName, StateData};
  180. handle_sync_event(get_has_gosterge, _From, StateName,
  181. #state{has_gosterge = HasGosterge} = StateData) ->
  182. {reply, HasGosterge, StateName, StateData};
  183. handle_sync_event(_Event, _From, StateName, StateData) ->
  184. {reply, {error, unknown_request}, StateName, StateData}.
  185. %% --------------------------------------------------------------------
  186. handle_info(_Info, StateName, StateData) ->
  187. {next_state, StateName, StateData}.
  188. %% --------------------------------------------------------------------
  189. terminate(_Reason, _StateName, _StatData) ->
  190. ok.
  191. %% --------------------------------------------------------------------
  192. code_change(_OldVsn, StateName, StateData, _Extra) ->
  193. {ok, StateName, StateData}.
  194. %% --------------------------------------------------------------------
  195. %%% Internal functions
  196. %% --------------------------------------------------------------------
  197. %% @spec handle_player_action(SeatNum, Action, StateName, StateData) ->
  198. %% {ok, Events, NextStateName, NextStateData} |
  199. %% {error, Reason}
  200. %% @end
  201. handle_player_action(PlayerId, i_have_gosterge, StateName,
  202. #state{players = Players, gosterge = Gosterge,
  203. gosterge_finish_list = GostFinishList} = StateData) when
  204. StateName == ?STATE_TAKE;
  205. StateName == ?STATE_DISCARD ->
  206. case get_player(PlayerId, Players) of
  207. #player{can_show_gosterge = true,
  208. hand = Hand} = Player ->
  209. case deck:member(Gosterge, Hand) of
  210. true ->
  211. case lists:member(PlayerId, GostFinishList) of
  212. false ->
  213. NewPlayer = Player#player{can_show_gosterge = false},
  214. NewPlayers = update_player(NewPlayer, Players),
  215. Events = [{has_gosterge, PlayerId}],
  216. {ok, Events, StateName, StateData#state{players = NewPlayers,
  217. has_gosterge = PlayerId}};
  218. true ->
  219. Events = [{gosterge_finish, PlayerId}],
  220. {ok, Events, ?STATE_FINISHED,
  221. StateData#state{has_gosterge = PlayerId}}
  222. end;
  223. false ->
  224. {error, no_gosterge}
  225. end;
  226. #player{can_show_gosterge = false} ->
  227. {error, action_disabled}
  228. end;
  229. handle_player_action(PlayerId, i_have_8_tashes, StateName,
  230. #state{players = Players, have_8_tashes_enabled = Enabled,
  231. have_8_tashes_list = Have8TashesList
  232. } = StateData) when
  233. StateName == ?STATE_TAKE;
  234. StateName == ?STATE_DISCARD ->
  235. case get_player(PlayerId, Players) of
  236. #player{can_show_8_tashes = true,
  237. hand = Hand} = Player when Enabled ->
  238. case find_8_tashes(Hand) of
  239. {ok, Value} ->
  240. NewPlayer = Player#player{can_show_8_tashes = false},
  241. NewPlayers = update_player(NewPlayer, Players),
  242. Events = [{has_8_tashes, PlayerId, Value}],
  243. {ok, Events, StateName,
  244. StateData#state{players = NewPlayers,
  245. have_8_tashes_list = [PlayerId | Have8TashesList]}};
  246. not_found ->
  247. {error, no_8_tashes}
  248. end;
  249. #player{} ->
  250. {error, action_disabled}
  251. end;
  252. handle_player_action(PlayerId, see_okey, ?STATE_TAKE,
  253. #state{cur_player = CurPlayerId,
  254. players = Players,
  255. okey = Okey} = StateData) ->
  256. #player{discarded = Discarded} = get_player(prev_id(CurPlayerId), Players),
  257. case deck:get(1, Discarded) of
  258. {Okey, _} ->
  259. Events = [{saw_okey, PlayerId}],
  260. {ok, Events, ?STATE_TAKE, StateData#state{okey_blocked = true}};
  261. _ ->
  262. {error, no_okey_discarded}
  263. end;
  264. handle_player_action(PlayerId, take_from_discarded, ?STATE_TAKE,
  265. #state{cur_player = CurPlayerId,
  266. players = Players,
  267. okey_blocked = Blocked} = StateData) ->
  268. if PlayerId == CurPlayerId ->
  269. if not Blocked ->
  270. case take_tash_from_discarded(PlayerId, Players) of
  271. {Tash, NewPlayers} ->
  272. Events = [{taked_from_discarded, PlayerId, Tash}],
  273. {ok, Events, ?STATE_DISCARD,
  274. StateData#state{players = NewPlayers, okey_blocked = false}};
  275. error ->
  276. {error, no_tash}
  277. end;
  278. true ->
  279. {error, blocked}
  280. end;
  281. true ->
  282. {error, not_your_order}
  283. end;
  284. handle_player_action(PlayerId, take_from_table, ?STATE_TAKE,
  285. #state{cur_player = CurPlayerId,
  286. players = Players,
  287. deck = Deck} = StateData) ->
  288. if PlayerId == CurPlayerId ->
  289. case take_tash_from_table(PlayerId, Players, Deck) of
  290. {Tash, NewPlayers, NewDeck} ->
  291. Events = [{taked_from_table, PlayerId, Tash}],
  292. {ok, Events, ?STATE_DISCARD,
  293. StateData#state{players = NewPlayers, deck = NewDeck, okey_blocked = false}};
  294. error ->
  295. {error, no_tash}
  296. end;
  297. true ->
  298. {error, not_your_order}
  299. end;
  300. handle_player_action(PlayerId, {discard, Tash}, StateName,
  301. #state{cur_player = CurPlayerId,
  302. players = Players,
  303. okey = Okey,
  304. deck = Deck} = StateData) when
  305. StateName == ?STATE_DISCARD ->
  306. case {PlayerId,StateName} of
  307. {CurPlayerId,?STATE_DISCARD} ->
  308. case discard_tash(Tash, PlayerId, Players) of
  309. error ->
  310. gas:info(?MODULE,"OKEY_NG_DESK Discard error. SeatNum: ~p. Tash: ~p", [PlayerId, Tash]),
  311. {error, no_such_tash};
  312. NewPlayers ->
  313. Events1 = [{tash_discarded, PlayerId, Tash}],
  314. case deck:size(Deck) of
  315. 0 ->
  316. Events = [no_winner_finish | Events1],
  317. {ok, Events, ?STATE_FINISHED,
  318. StateData#state{players = NewPlayers}};
  319. _ ->
  320. NextPlayerId = next_id(CurPlayerId),
  321. #player{discarded = Discarded} = get_player(CurPlayerId, Players),
  322. wf:info(?MODULE,"Looking for Okey in Discarded Tower for Player ~p",[Discarded]),
  323. EnableOkey = if Discarded /= [] ->
  324. case deck:get(1, Discarded) of {Okey, _} -> true; _ -> false end; true -> false end,
  325. Events = [{next_player,NextPlayerId,EnableOkey} | Events1],
  326. {ok, Events, ?STATE_TAKE,
  327. StateData#state{players = NewPlayers, cur_player = NextPlayerId}}
  328. end
  329. end;
  330. {_,?STATE_DISCARD} ->
  331. {error, not_your_order}
  332. end;
  333. handle_player_action(PlayerId, wrong_reveal, StateName, #state{cur_player = CurPlayerId} = State) ->
  334. case PlayerId == CurPlayerId of
  335. true -> {ok, [{wrong_reveal,PlayerId}], StateName, State};
  336. false -> {error, not_your_order} end;
  337. handle_player_action(PlayerId, {reveal, Tash, TashPlaces}, StateName = ?STATE_DISCARD,
  338. #state{cur_player = CurPlayerId,
  339. players = Players
  340. } = StateData) ->
  341. wf:info(?MODULE,"DESK PLAYER ACTION REVEAL STATE ~p",[StateName]),
  342. if PlayerId == CurPlayerId ->
  343. case discard_tash(Tash, PlayerId, Players) of
  344. error ->
  345. {error, no_such_tash};
  346. NewPlayers ->
  347. RevealHand = tash_places_to_hand(TashPlaces),
  348. #player{hand = PlayerHand} = get_player(PlayerId, NewPlayers),
  349. case is_same_hands(RevealHand, PlayerHand) of
  350. true ->
  351. Events = [{reveal, PlayerId, TashPlaces, Tash}],
  352. {ok, Events, ?STATE_FINISHED,
  353. StateData#state{players = NewPlayers}};
  354. false ->
  355. {error, hand_not_match}
  356. end
  357. end;
  358. true ->
  359. {error, not_your_order}
  360. end;
  361. handle_player_action(_PlayerId, _Action, _StateName, _StateData) ->
  362. gas:error(?MODULE,"OKEY_NG_DESK Invalid action passed. Player: ~p.~n Action: ~p. StateName: ~p.",
  363. [_PlayerId, _Action, _StateName]),
  364. {error, invalid_action}.
  365. %%===================================================================
  366. %% get_param(Id, Params) -> Value
  367. get_param(Id, Params) ->
  368. {_, Value} =lists:keyfind(Id, 1, Params),
  369. Value.
  370. %% TODO: Implement the validator
  371. validate_params(_Hands, _Deck, _Gosterge, _CurPlayer, _GostFinishList, _Have8TashedEnabled) ->
  372. ok.
  373. init_players(Hands) ->
  374. F = fun(Hand, Id) ->
  375. {#player{id = Id, %% Seat num
  376. hand = Hand,
  377. discarded = deck:init_deck(empty),
  378. finished_by_okey = false,
  379. can_show_gosterge = true,
  380. can_show_8_tashes = true},
  381. Id+1}
  382. end,
  383. {Players, _} = lists:mapfoldl(F, 1, Hands),
  384. Players.
  385. %% gosterge_to_okey(GostergyTash) -> OkeyTash
  386. gosterge_to_okey({Color, Value}) ->
  387. if Value == 13 -> {Color, 1};
  388. true -> {Color, Value + 1}
  389. end.
  390. %% next_id(Id) -> NextId
  391. next_id(4) -> 1;
  392. next_id(Id) -> Id + 1.
  393. %% prev_id(Id) -> PrevId
  394. prev_id(1) -> 4;
  395. prev_id(Id) -> Id - 1.
  396. %% take_tash_from_discarded(PlayerId, Players) -> {Tash, NewPlayers} | error
  397. take_tash_from_discarded(PlayerId, Players) ->
  398. #player{discarded = Discarded} = PrevPlayer = get_player(prev_id(PlayerId), Players),
  399. case deck:size(Discarded) of
  400. 0 ->
  401. error;
  402. _ ->
  403. {Taken, RestDiscarded} = deck:pop(1, Discarded),
  404. NewPrevPlayer = PrevPlayer#player{discarded = RestDiscarded},
  405. #player{hand = Hand} = Player = get_player(PlayerId, Players),
  406. NewPlayer = Player#player{hand = deck:push(Taken, Hand),
  407. can_show_gosterge = false},
  408. NewPlayers1 = update_player(NewPrevPlayer, Players),
  409. NewPlayers2 = update_player(NewPlayer, NewPlayers1),
  410. [Tash] = deck:to_list(Taken),
  411. {Tash, NewPlayers2}
  412. end.
  413. %% take_tash_from_table(PlayerId, Players, Deck) -> {Tash, NewPlayers, NewDeck} | error
  414. take_tash_from_table(PlayerId, Players, Deck) ->
  415. case deck:size(Deck) of
  416. 0 ->
  417. error;
  418. _ ->
  419. {Taken, NewDeck} = deck:pop(1, Deck),
  420. #player{hand = Hand} = Player = get_player(PlayerId, Players),
  421. NewPlayer = Player#player{hand = deck:push(Taken, Hand),
  422. can_show_gosterge = false},
  423. NewPlayers = update_player(NewPlayer, Players),
  424. [Tash] = deck:to_list(Taken),
  425. {Tash, NewPlayers, NewDeck}
  426. end.
  427. %% discard_tash(Tash, PlayerId, Players) -> NewPlayers
  428. discard_tash(Tash, PlayerId, Players) ->
  429. #player{hand = Hand, discarded = Discarded} = Player = get_player(PlayerId, Players),
  430. case deck:del_first(Tash, Hand) of
  431. {ok, NewHand} ->
  432. NewDiscarded = deck:put(Tash, 1, Discarded),
  433. NewPlayer = Player#player{hand = NewHand, discarded = NewDiscarded},
  434. update_player(NewPlayer, Players);
  435. error ->
  436. error
  437. end.
  438. %% tash_places_to_hand(TashPlaces) -> Hand
  439. tash_places_to_hand(TashPlaces) ->
  440. Elements = [P || P <- lists:flatten(TashPlaces), P =/= null],
  441. deck:from_list(Elements).
  442. %% get_player(PlayerId, Players) -> Player
  443. get_player(PlayerId, Players) ->
  444. #player{} = lists:keyfind(PlayerId, #player.id, Players).
  445. %% update_player(Player, Players) -> NewPlayers
  446. update_player(#player{id = Id} = Player, Players) ->
  447. lists:keyreplace(Id, #player.id, Players, Player).
  448. %% is_same_hands(Hand1, Hand2) -> boolean()
  449. is_same_hands(Hand1, Hand2) ->
  450. L1 = lists:sort(deck:to_list(Hand1)),
  451. L2 = lists:sort(deck:to_list(Hand2)),
  452. L1 == L2.
  453. %% find_8_tashes(Hand) -> {ok, Value} | not_found
  454. find_8_tashes(Hand) -> find_8_tashes2(deck:to_list(Hand)).
  455. find_8_tashes2(Hand) when length(Hand) < 8 -> not_found;
  456. find_8_tashes2([{_, Value} | Rest]) ->
  457. case count_tashes_with_value(Value, Rest) of
  458. 7 -> {ok, Value};
  459. _ -> find_8_tashes2(Rest)
  460. end.
  461. count_tashes_with_value(Value, Tashes) ->
  462. length([1 || {_, TVal} <- Tashes, TVal == Value]).