tavla_desk.erl 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. %%% -------------------------------------------------------------------
  2. %%% Author : Sergei Polkovnikov <serge.polkovnikov@gmail.com>
  3. %%% Description : The tavla game rules implementation
  4. %%%
  5. %%% Created : Jan 14, 2013
  6. %%% -------------------------------------------------------------------
  7. -module(tavla_desk).
  8. -behaviour(gen_fsm).
  9. %% Board model schema:
  10. %% 13 14 15 16 17 18 19 20 21 22 23 24 BO
  11. %% BB
  12. %%
  13. %% WB
  14. %% 12 11 10 09 08 07 06 05 04 03 02 01 WO
  15. %% Parameters:
  16. %% home_hit_and_run - specifies is the "hit and run" allowed in the home.
  17. %% Type : enabled | disabled
  18. %% bearoff_waste_moves - specifies are "waste" moves allowed in the bear-off phase.
  19. %% Waste move means a normal move when a bear-off can be done.
  20. %% Type: enabled | disabled
  21. %% first_move - a color of player who should make first move.
  22. %% Type: black | white
  23. %% Options:
  24. %% board - an initial board definition. Describes how the checkers are placed on the
  25. %% board. All table position must be specified in the definition. The total
  26. %% number of checkers must be exectly 15 per each color. The first position
  27. %% in the white home is 1 and the first position in the black home is 24. White
  28. %% checkers goes counter-clockwise, and the black ones clockwise.
  29. %% If the option is not defined then the regular board definition will be applyed.
  30. %% Type: [{Position, State}]
  31. %% Position = 1-24, wb, bb, wo, bo
  32. %% State = empty | {Color, CheckersNumber}
  33. %% Color = black | white
  34. %% CheckersNumber = 1-15
  35. %% dice - initial dice value. Define this option if dice was defined by the first move competition
  36. %% procedure. So the player should do moves at start instead roll.
  37. %% Type: {Die1, Die2}
  38. %% Die1 = Die2 = 1-6
  39. %% Players actions || Errors
  40. %% {roll, Die1, Die2} || not_your_order, invalid_action
  41. %% Die1 = Die2 = 1-6 ||
  42. %% ||
  43. %% {move, Moves} || not_your_order, invalid_action, too_many_moves,
  44. %% Moves = [{From, To}] || {position_occupied, Move, RestMoves},
  45. %% From = wb, bb, 1-24 || {waste_move_disabled, Move, RestMoves},
  46. %% To = wo, bo, 1-24 || {hit_and_run_disabled, Move, RestMoves},
  47. %% || {not_bear_off_mode, Move, RestMoves},
  48. %% || {no_checker, Move, RestMoves},
  49. %% || {invalid_move, Move, RestMoves},
  50. %% || {move_from_bar_first, Move, RestMoves}
  51. %% Outgoing events:
  52. %% {next_player, Color}
  53. %% Color = black | white
  54. %% {rolls, Color, Die1, Die2}
  55. %% {moves, Color, Moves}
  56. %% Moves = [{Type, From, To, Pips}]
  57. %% Type = move | hit
  58. %% Pips = 1-6
  59. %% {win, Color, Condition}
  60. %% Condition = normal | mars
  61. %% External exports
  62. -export([
  63. start/1,
  64. stop/1,
  65. player_action/3
  66. ]).
  67. %% gen_fsm callbacks
  68. -export([init/1, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
  69. -define(CHECKERS_NUM, 15).
  70. -define(WHITE_OUT, wo).
  71. -define(BLACK_OUT, bo).
  72. -define(WHITE_BAR, wb).
  73. -define(BLACK_BAR, bb).
  74. -define(WHITE, white).
  75. -define(BLACK, black).
  76. -define(STATE_WAIT_ROLL, state_wait_roll).
  77. -define(STATE_WAIT_MOVE, state_wait_move).
  78. -define(STATE_FINISHED, state_finished).
  79. -record(state,
  80. {home_hit_and_run_enabled :: boolean(),
  81. bearoff_waste_moves_enabled :: boolean(),
  82. first_move :: black | white,
  83. board :: dict(),
  84. pips_list :: undefined | list(integer()),
  85. hitted_home_positions :: list(),
  86. current :: black | white,
  87. finish_conditions :: undefined | {black | white, normal | mars}
  88. }).
  89. %% ====================================================================
  90. %% External functions
  91. %% ====================================================================
  92. start(Params) -> gen_fsm:start(?MODULE, Params, []).
  93. player_action(Desk, Color, Action) ->
  94. gen_fsm:sync_send_all_state_event(Desk, {player_action, Color, Action}).
  95. stop(Desk) -> gen_fsm:send_all_state_event(Desk, stop).
  96. %% ====================================================================
  97. %% Server functions
  98. %% ====================================================================
  99. % --------------------------------------------------------------------
  100. init(Params) ->
  101. HomeHitAndRun = get_param(home_hit_and_run, Params),
  102. BearoffWasteMoves = get_param(bearoff_waste_moves, Params),
  103. FirstMove = get_param(first_move, Params),
  104. BoardSpec = get_option(board, Params, undefined),
  105. Dice = get_option(dice, Params, undefined),
  106. validate_params(HomeHitAndRun, BearoffWasteMoves, FirstMove, BoardSpec, Dice),
  107. Board = if BoardSpec == undefined -> init_board(initial_board());
  108. true -> init_board(BoardSpec)
  109. end,
  110. State = #state{home_hit_and_run_enabled = HomeHitAndRun == enabled,
  111. bearoff_waste_moves_enabled = BearoffWasteMoves == enabled,
  112. first_move = FirstMove,
  113. current = FirstMove,
  114. board = Board,
  115. pips_list = undefined,
  116. hitted_home_positions = []
  117. },
  118. case Dice of
  119. undefined ->
  120. {ok, ?STATE_WAIT_ROLL, State};
  121. {Die1, Die2} ->
  122. PipsList = pips_list(Die1, Die2),
  123. {ok, ?STATE_WAIT_MOVE, State#state{pips_list = PipsList}}
  124. end.
  125. %% --------------------------------------------------------------------
  126. handle_event(_Event, StateName, StateData) ->
  127. {next_state, StateName, StateData}.
  128. %% --------------------------------------------------------------------
  129. handle_sync_event({player_action, SeatNum, Action}, _From, StateName, StateData) ->
  130. case handle_player_action(SeatNum, Action, StateName, StateData) of
  131. {ok, Events, NewStateName, NewStateData} ->
  132. {reply, {ok, lists:reverse(Events)}, NewStateName, NewStateData};
  133. {error, Reason} ->
  134. {reply, {error, Reason}, StateName, StateData}
  135. end;
  136. handle_sync_event(_Event, _From, StateName, StateData) ->
  137. Reply = ok,
  138. {reply, Reply, StateName, StateData}.
  139. %% --------------------------------------------------------------------
  140. handle_info(_Info, StateName, StateData) ->
  141. {next_state, StateName, StateData}.
  142. %% --------------------------------------------------------------------
  143. terminate(_Reason, _StateName, _StatData) ->
  144. ok.
  145. %% --------------------------------------------------------------------
  146. code_change(_OldVsn, StateName, StateData, _Extra) ->
  147. {ok, StateName, StateData}.
  148. %% --------------------------------------------------------------------
  149. %% @spec handle_player_action(Color, Action, StateName, StateData) ->
  150. %% {ok, Events, NextStateName, NextStateData} |
  151. %% {error, Reason}
  152. %% @end
  153. handle_player_action(PlayerId, {roll, Die1, Die2}, ?STATE_WAIT_ROLL = StateName,
  154. #state{current = Current} = StateData) ->
  155. if PlayerId == Current ->
  156. process_roll(PlayerId, Die1, Die2, StateName, StateData);
  157. true ->
  158. {error, not_your_order}
  159. end;
  160. handle_player_action(PlayerId, {move, Moves}, ?STATE_WAIT_MOVE = StateName,
  161. #state{current = Current} = StateData) ->
  162. if PlayerId == Current ->
  163. process_moves(PlayerId, Moves, StateName, StateData);
  164. true ->
  165. {error, not_your_order}
  166. end;
  167. handle_player_action(_PlayerId, _Action, _StateName, _StateData) ->
  168. {error, invalid_action}.
  169. %% --------------------------------------------------------------------
  170. %%% Internal functions
  171. %% --------------------------------------------------------------------
  172. process_roll(PlayerId, Die1, Die2, _StateName,
  173. #state{board = Board, bearoff_waste_moves_enabled = BearoffWasteMovesEnabled,
  174. home_hit_and_run_enabled = HomeHitAndRunEnabled} = StateData) ->
  175. PipsList = if Die1 == Die2 -> [Die1, Die1, Die1, Die1];
  176. true -> [Die1, Die2]
  177. end,
  178. case is_any_move_available(PlayerId, PipsList, Board, BearoffWasteMovesEnabled,
  179. HomeHitAndRunEnabled, []) of
  180. true ->
  181. Events = [{rolls, PlayerId, Die1, Die2}],
  182. {ok, Events, ?STATE_WAIT_MOVE, StateData#state{pips_list = PipsList}};
  183. false ->
  184. Opponent = opponent(PlayerId),
  185. Events = [{next_player, Opponent} , {rolls, PlayerId, Die1, Die2}],
  186. {ok, Events, ?STATE_WAIT_ROLL, StateData#state{current = Opponent}}
  187. end.
  188. process_moves(PlayerId, Moves, _StateName,
  189. #state{board = Board, pips_list = PipsList,
  190. bearoff_waste_moves_enabled = BearoffWasteMovesEnabled,
  191. home_hit_and_run_enabled = HomeHitAndRunEnabled,
  192. hitted_home_positions = HittedHomePositions} = StateData) ->
  193. case apply_moves(PlayerId, PipsList, Moves, Board, BearoffWasteMovesEnabled,
  194. HomeHitAndRunEnabled, HittedHomePositions) of
  195. {ok, NewBoard, NewPipsList, NewHittedHomePositions, RealMoves} ->
  196. MovesEvents = [{moves, PlayerId, lists:reverse(RealMoves)}],
  197. case is_game_finished(PlayerId, NewBoard) of
  198. {yes, Condition} ->
  199. Events = [{win, PlayerId, Condition} | MovesEvents],
  200. {ok, Events, ?STATE_FINISHED, StateData#state{board = NewBoard,
  201. finish_conditions = {PlayerId, Condition}}};
  202. no ->
  203. AnyMoveAvailable = NewPipsList =/= [] andalso
  204. is_any_move_available(PlayerId, NewPipsList, NewBoard, BearoffWasteMovesEnabled,
  205. HomeHitAndRunEnabled, NewHittedHomePositions),
  206. if AnyMoveAvailable ->
  207. {ok, MovesEvents, ?STATE_WAIT_MOVE,
  208. StateData#state{board = NewBoard,
  209. pips_list = NewPipsList,
  210. hitted_home_positions = NewHittedHomePositions}};
  211. true ->
  212. Opponent = opponent(PlayerId),
  213. Events = [{next_player, Opponent} | MovesEvents],
  214. {ok, Events, ?STATE_WAIT_ROLL,
  215. StateData#state{board = NewBoard,
  216. pips_list = [],
  217. hitted_home_positions = [],
  218. current = Opponent}}
  219. end
  220. end;
  221. {error, Reason} ->
  222. {error, Reason}
  223. end.
  224. %% initial_board() -> BoardList
  225. initial_board() ->
  226. [{01, {?BLACK, 2}},
  227. {02, empty},
  228. {03, empty},
  229. {04, empty},
  230. {05, empty},
  231. {06, {?WHITE, 5}},
  232. {07, empty},
  233. {08, {?WHITE, 3}},
  234. {09, empty},
  235. {10, empty},
  236. {11, empty},
  237. {12, {?BLACK, 5}},
  238. {13, {?WHITE, 5}},
  239. {14, empty},
  240. {15, empty},
  241. {16, empty},
  242. {17, {?BLACK, 3}},
  243. {18, empty},
  244. {19, {?BLACK, 5}},
  245. {20, empty},
  246. {21, empty},
  247. {22, empty},
  248. {23, empty},
  249. {24, {?WHITE, 2}},
  250. {?WHITE_OUT, empty},
  251. {?BLACK_OUT, empty},
  252. {?WHITE_BAR, empty},
  253. {?BLACK_BAR, empty}
  254. ].
  255. init_board(Spec) ->
  256. EmptyBoard = empty_board(),
  257. F = fun({Pos, Value}, Acc) ->
  258. dict:store(Pos, Value, Acc)
  259. end,
  260. lists:foldl(F, EmptyBoard, Spec).
  261. empty_board() ->
  262. dict:from_list([{Pos, empty} || Pos <- [wb, bb, wo,bo | lists:seq(1, 24)]]).
  263. %% get_param(Id, Params) -> Value
  264. get_param(Id, Params) ->
  265. {_, Value} = lists:keyfind(Id, 1, Params),
  266. Value.
  267. %% get_option(Id, Params, DefaultValue) -> Value
  268. get_option(Id, Params, DefaultValue) ->
  269. case lists:keyfind(Id, 1, Params) of
  270. {_, Value} -> Value;
  271. false -> DefaultValue
  272. end.
  273. %% TODO: Implement the validator
  274. validate_params(_HomeHitAndRun, _WastePipsDuringBearoff, _FirstMove, _Desk, _Dice) ->
  275. ok.
  276. pips_list(Die, Die) -> [Die, Die, Die, Die];
  277. pips_list(Die1, Die2) -> [Die1, Die2].
  278. %% opponent(Color1) -> Color2
  279. opponent(?WHITE) -> ?BLACK;
  280. opponent(?BLACK) -> ?WHITE.
  281. %% is_game_finished(Color, Board) -> {yes, normal | mars} | no
  282. is_game_finished(Color, Board) ->
  283. case get_checkers(out_position(Color), Board) of
  284. {Color, ?CHECKERS_NUM} ->
  285. case get_checkers(out_position(opponent(Color)), Board) of
  286. empty -> {yes, mars};
  287. _ -> {yes, normal}
  288. end;
  289. _ -> no
  290. end.
  291. %% get_checkers(Pos, Board) -> {Color, Num} | empty
  292. get_checkers(Pos, Board) -> dict:fetch(Pos, Board).
  293. %% move_checker(From, To, Board) -> NewBoard
  294. move_checker(From, To, Board) ->
  295. {Color, FromNum} = dict:fetch(From, Board),
  296. NewBoard = if FromNum == 1 -> dict:store(From, empty, Board);
  297. true -> dict:store(From, {Color, FromNum - 1}, Board)
  298. end,
  299. case dict:fetch(To, NewBoard) of
  300. empty -> dict:store(To, {Color, 1}, NewBoard);
  301. {Color, ToNum} -> dict:store(To, {Color, ToNum + 1}, NewBoard)
  302. end.
  303. apply_moves(_Color, PipsList, Moves, _Board, _BearoffWasteMoveEnabled,
  304. _HomeHitAndRunEnabled, _HittedHomePositions)
  305. when length(Moves) > length(PipsList) ->
  306. {error, too_many_moves};
  307. apply_moves(Color, PipsList, Moves, Board,
  308. BearoffWasteMoveEnabled, HomeHitAndRunEnabled, HittedHomePositions) ->
  309. apply_moves2(Color, PipsList, Moves, Board, _MoveEvents = [], HittedHomePositions,
  310. BearoffWasteMoveEnabled, HomeHitAndRunEnabled).
  311. apply_moves2(_Color, PipsList, _Moves = [], Board, MoveEvents, HittedHomePositions,
  312. _BearoffWasteMoveEnabled, _HomeHitAndRunEnabled) ->
  313. {ok, Board, PipsList, HittedHomePositions, MoveEvents};
  314. apply_moves2(Color, PipsList, [{From, To} = Move | RestMoves], Board, MoveEvents,
  315. HittedHomePositions, BearoffWasteMoveEnabled, HomeHitAndRunEnabled) ->
  316. case take_pips(Color, Move, PipsList, Board) of
  317. {ok, Pips, NewPipsList} ->
  318. case check_move_posibility(Color, From, To, PipsList, Board, BearoffWasteMoveEnabled,
  319. HomeHitAndRunEnabled, HittedHomePositions) of
  320. ok ->
  321. NewBoard = move_checker(From, To, Board),
  322. NewMoveEvents = [{move, From, To, Pips} | MoveEvents],
  323. apply_moves2(Color, NewPipsList, RestMoves, NewBoard, NewMoveEvents,
  324. HittedHomePositions, BearoffWasteMoveEnabled, HomeHitAndRunEnabled);
  325. hit ->
  326. NewBoard1 = move_checker(To, bar_position(opponent(Color)), Board),
  327. NewBoard2 = move_checker(From, To, NewBoard1),
  328. {HomeMin, HomeMax} = home_range(Color),
  329. NewHittedHomePositions = if To >= HomeMin andalso To =< HomeMax ->
  330. [To | HittedHomePositions];
  331. true -> HittedHomePositions end,
  332. NewMoveEvents = [{hit, From, To, Pips} | MoveEvents],
  333. apply_moves2(Color, NewPipsList, RestMoves, NewBoard2, NewMoveEvents,
  334. NewHittedHomePositions, BearoffWasteMoveEnabled,
  335. HomeHitAndRunEnabled);
  336. {error, occupied} -> {error, {position_occupied, Move, RestMoves}};
  337. {error, waste_move} -> {error, {waste_move_disabled, Move, RestMoves}};
  338. {error, hit_and_run} -> {error, {hit_and_run_disabled, Move, RestMoves}};
  339. {error, not_bear_off_mode} -> {error, {not_bear_off_mode, Move, RestMoves}};
  340. {error, no_checker} -> {error, {no_checker, Move, RestMoves}};
  341. {error, move_from_bar_first} -> {error, {move_from_bar_first, Move, RestMoves}}
  342. end;
  343. error -> {error, {invalid_move, Move, RestMoves}}
  344. end.
  345. is_any_move_available(Color, Pips, Board, BearoffWasteMoveEnabled,
  346. HomeHitAndRunEnabled, HitedHomePositions) ->
  347. Bar = bar_position(Color),
  348. case get_checkers(Bar, Board) of
  349. {Color, _} -> is_any_move_available_bar(Color, Pips, Board);
  350. empty -> is_any_move_available_desk(Color, Pips, Board, BearoffWasteMoveEnabled,
  351. HomeHitAndRunEnabled, HitedHomePositions)
  352. end.
  353. is_any_move_available_bar(Color, Pips, Board) ->
  354. F = fun(P) ->
  355. case check_destination(Color, new_pos(Color, bar_position(Color), P), Board) of
  356. ok -> true;
  357. hit -> true;
  358. occupied -> false
  359. end
  360. end,
  361. lists:any(F, Pips).
  362. is_any_move_available_desk(Color, Pips, Board, BearoffWasteMoveEnabled,
  363. HomeHitAndRunEnabled, HitedHomePositions) ->
  364. F = fun(Pos) ->
  365. F2 = fun(P) ->
  366. case check_move_posibility(Color, Pos, new_pos(Color, Pos, P), Pips,
  367. Board, BearoffWasteMoveEnabled,
  368. HomeHitAndRunEnabled, HitedHomePositions) of
  369. ok -> true;
  370. hit -> true;
  371. {error, _} -> false
  372. end
  373. end,
  374. lists:any(F2, Pips)
  375. end,
  376. lists:any(F, route(Color)).
  377. %% check_move_posibility/8 -> ok | hit | {error, Reason}
  378. check_move_posibility(Color, From, To, Pips, Board, BearoffWasteMoveEnabled,
  379. HomeHitAndRunEnabled, HitedHomePositions) ->
  380. BarPos = bar_position(Color),
  381. CheckersOnBar = case get_checkers(BarPos, Board) of
  382. {Color, _} -> true;
  383. empty -> false
  384. end,
  385. if From =/= BarPos andalso CheckersOnBar ->
  386. {error, move_from_bar_first};
  387. true ->
  388. check_checker(Color, From, To, Pips, Board, BearoffWasteMoveEnabled,
  389. HomeHitAndRunEnabled, HitedHomePositions)
  390. end.
  391. check_checker(Color, From, To, Pips, Board, BearoffWasteMoveEnabled,
  392. HomeHitAndRunEnabled, HitedHomePositions) ->
  393. case get_checkers(From, Board) of
  394. {Color, _} -> check_home_hit_and_run(Color, From, To, Pips, Board, BearoffWasteMoveEnabled,
  395. HomeHitAndRunEnabled, HitedHomePositions);
  396. _ -> {error, no_checker}
  397. end.
  398. check_home_hit_and_run(Color, From, To, Pips, Board, BearoffWasteMoveEnabled,
  399. HomeHitAndRunEnabled, HitedHomePositions) ->
  400. TestPassed = if HomeHitAndRunEnabled -> true;
  401. true ->
  402. {_, Num} = get_checkers(From, Board),
  403. if Num > 1 -> true;
  404. true -> not lists:member(From, HitedHomePositions)
  405. end
  406. end,
  407. if TestPassed -> check_waste_move(Color, From, To, Pips, Board, BearoffWasteMoveEnabled,
  408. HomeHitAndRunEnabled, HitedHomePositions);
  409. true -> {error, hit_and_run}
  410. end.
  411. check_waste_move(Color, From, To, Pips, Board, BearoffWasteMoveEnabled,
  412. HomeHitAndRunEnabled, HitedHomePositions) ->
  413. BearOffMode = detect_bearoff_mode(Color, Board),
  414. OutPos = out_position(Color),
  415. TestPassed = if BearoffWasteMoveEnabled -> true;
  416. true ->
  417. case BearOffMode of
  418. false -> true;
  419. true ->
  420. case To == OutPos of
  421. true -> true;
  422. false -> not out_possible(Color, Pips, Board)
  423. end
  424. end
  425. end,
  426. if TestPassed -> check_destination_pos(Color, To, Board, BearOffMode, OutPos);
  427. true -> {error, waste_move}
  428. end.
  429. check_destination_pos(Color, To, Board, BearOffMode, OutPos) ->
  430. if To == OutPos andalso not BearOffMode ->
  431. {error, not_bear_off_mode};
  432. true ->
  433. case check_destination(Color, To, Board) of
  434. ok -> ok;
  435. hit -> hit;
  436. occupied -> {error, occupied}
  437. end
  438. end.
  439. out_possible(Color, Pips, Board) ->
  440. out_pip_exists(Color, Pips, Board) orelse
  441. far_pip_exists(Color, Pips, Board).
  442. far_pip_exists(Color, Pips, Board) ->
  443. MaxPip = lists:max(Pips),
  444. not more_far_checkers_exist(Color, MaxPip, Board).
  445. out_pip_exists(Color, Pips, Board) ->
  446. F = fun(Pip) -> case get_checkers(prev_pos(Color, ?WHITE_OUT, Pip), Board) of
  447. {Color, _} -> true;
  448. _ -> false
  449. end
  450. end,
  451. lists:any(F, Pips).
  452. detect_bearoff_mode(Color, Board) ->
  453. Out = out_position(Color),
  454. Bar = bar_position(Color),
  455. {HomeMin, HomeMax} = home_range(Color),
  456. F = fun({Pos, {C, _}}) when C == Color, Pos == Out -> true;
  457. ({Pos, {C, _}}) when C == Color, Pos == Bar -> false;
  458. ({Pos, {C, _}}) when C == Color -> Pos >= HomeMin andalso Pos =< HomeMax;
  459. (_) -> true
  460. end,
  461. lists:all(F, dict:to_list(Board)).
  462. out_position(?WHITE) -> ?WHITE_OUT;
  463. out_position(?BLACK) -> ?BLACK_OUT.
  464. bar_position(?WHITE) -> ?WHITE_BAR;
  465. bar_position(?BLACK) -> ?BLACK_BAR.
  466. home_range(?WHITE) -> {1, 6};
  467. home_range(?BLACK) -> {19, 24}.
  468. new_pos(?WHITE, From, Pips) ->
  469. if From == ?WHITE_BAR -> 25 - Pips;
  470. (From - Pips) > 0 -> From - Pips;
  471. (From - Pips) =< 0 -> ?WHITE_OUT
  472. end;
  473. new_pos(?BLACK, From, Pips) ->
  474. if From == ?BLACK_BAR -> Pips;
  475. (From + Pips) < 25 -> From + Pips;
  476. (From + Pips) >= 25 -> ?BLACK_OUT
  477. end.
  478. prev_pos(?WHITE, At, Pips) ->
  479. if At == ?WHITE_OUT -> Pips;
  480. is_integer(At) -> At + Pips
  481. end;
  482. prev_pos(?BLACK, At, Pips) ->
  483. if At == ?BLACK_OUT -> 25 - Pips;
  484. is_integer(At) -> At - Pips
  485. end.
  486. route(?WHITE) -> lists:seq(24, 1, -1);
  487. route(?BLACK) -> lists:seq(1, 24).
  488. check_destination(Color, To, Board) ->
  489. OpColor = opponent(Color),
  490. case get_checkers(To, Board) of
  491. empty -> ok;
  492. {Color, _} -> ok;
  493. {OpColor, 1} -> hit;
  494. {OpColor, _} -> occupied
  495. end.
  496. %% take_pips(Color, {From, To}, Pips, BearoffMode) -> {ok, NewPips} | error
  497. take_pips(Color, {From, To}, _Pips, _BearoffMode)
  498. when From == ?WHITE_OUT;
  499. From == ?BLACK_OUT;
  500. From == To;
  501. From == ?WHITE_BAR andalso To == ?WHITE_OUT;
  502. From == ?BLACK_BAR andalso To == ?BLACK_OUT;
  503. Color == ?WHITE andalso (From == ?BLACK_BAR orelse To == ?BLACK_OUT);
  504. Color == ?BLACK andalso (From == ?WHITE_BAR orelse To == ?WHITE_OUT)
  505. ->
  506. error;
  507. take_pips(Color, {From, To}, PipsList, Board) ->
  508. Dist = if is_integer(From), is_integer(To) -> abs(To - From);
  509. From == ?WHITE_BAR -> 25 - To;
  510. From == ?BLACK_BAR -> To;
  511. To == ?WHITE_OUT -> From;
  512. To == ?BLACK_OUT -> 25 - From
  513. end,
  514. case find_pips(Color, Dist, To, PipsList, Board) of
  515. {ok, Pips} -> {ok, Pips, lists:delete(Pips, PipsList)};
  516. error -> error
  517. end.
  518. %% find_pips(Color, Dist, To, PipsList, Board) -> {ok, Pips} | error
  519. find_pips(Color, Dist, To, PipsList, Board) ->
  520. case lists:member(Dist, PipsList) of
  521. true -> {ok, Dist};
  522. false ->
  523. BearoffMode = detect_bearoff_mode(Color, Board),
  524. if BearoffMode ->
  525. Out = out_position(Color),
  526. if To == Out ->
  527. case more_far_checkers_exist(Color, Dist, Board) of
  528. true -> error;
  529. false ->
  530. BiggestPips = lists:max(PipsList),
  531. if BiggestPips >= Dist -> {ok, BiggestPips};
  532. true -> error
  533. end
  534. end;
  535. true -> error
  536. end;
  537. true -> error
  538. end
  539. end.
  540. %% more_far_checkers_exist(Color, Dist, Board) -> boolean()
  541. more_far_checkers_exist(Color, Dist, Board) ->
  542. {HomeMin, HomeMax} = home_range(Color),
  543. {RangeMin, RangeMax} = if Color == ?WHITE -> {HomeMin + Dist, HomeMax};
  544. Color == ?BLACK -> {HomeMin, HomeMax - Dist}
  545. end,
  546. F = fun(Pos) ->
  547. case get_checkers(Pos, Board) of
  548. {Color, _} -> true;
  549. _ -> false
  550. end
  551. end,
  552. lists:any(F, lists:seq(RangeMin, RangeMax)).