summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornsensfel <SpamShield0@noot-noot.org>2018-07-11 17:54:14 +0200
committernsensfel <SpamShield0@noot-noot.org>2018-07-11 17:54:14 +0200
commitfde827cba1ff3d889135c74ee1978098465fd200 (patch)
treed6022f800aa8226bf79a26a19189965c8cbfb2fe /src/battle/struct
parentdf59024199c387903e3d4a901171939a358489d3 (diff)
"Battlemap" -> "Battle".
Diffstat (limited to 'src/battle/struct')
-rw-r--r--src/battle/struct/btl_attack.erl306
-rw-r--r--src/battle/struct/btl_battle.erl216
-rw-r--r--src/battle/struct/btl_battle_action.erl114
-rw-r--r--src/battle/struct/btl_battlemap.erl100
-rw-r--r--src/battle/struct/btl_character.erl290
-rw-r--r--src/battle/struct/btl_character_turn_data.erl115
-rw-r--r--src/battle/struct/btl_character_turn_request.erl84
-rw-r--r--src/battle/struct/btl_character_turn_update.erl85
-rw-r--r--src/battle/struct/btl_direction.erl38
-rw-r--r--src/battle/struct/btl_location.erl90
-rw-r--r--src/battle/struct/btl_player.erl104
-rw-r--r--src/battle/struct/btl_player_turn.erl106
-rw-r--r--src/battle/struct/btl_tile.erl124
-rw-r--r--src/battle/struct/btl_turn_result.erl215
14 files changed, 1987 insertions, 0 deletions
diff --git a/src/battle/struct/btl_attack.erl b/src/battle/struct/btl_attack.erl
new file mode 100644
index 0000000..aa7659f
--- /dev/null
+++ b/src/battle/struct/btl_attack.erl
@@ -0,0 +1,306 @@
+-module(btl_attack).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-type order() :: ('first' | 'second' | 'counter').
+-type precision() :: ('misses' | 'grazes' | 'hits').
+
+-record
+(
+ attack,
+ {
+ order :: order(),
+ precision :: precision(),
+ is_critical :: boolean(),
+ is_parry :: boolean(),
+ damage :: non_neg_integer()
+ }
+).
+
+-opaque type() :: #attack{}.
+-type maybe_type() :: ('nothing' | type()).
+-opaque step() :: {order(), boolean()}.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export_type([type/0, maybe_type/0, step/0]).
+
+-export
+(
+ [
+ get_sequence/3,
+ get_description_of/3,
+ apply_to_healths/3
+ ]
+).
+
+-export
+(
+ [
+ encode/1
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec roll_precision
+ (
+ sh_statistics:type(),
+ sh_statistics:type()
+ )
+ -> precision().
+roll_precision (AttackerStatistics, DefenderStatistics) ->
+ DefenderDodges = sh_statistics:get_dodges(DefenderStatistics),
+ AttackerAccuracy = sh_statistics:get_accuracy(AttackerStatistics),
+ MissChance = max(0, (DefenderDodges - AttackerAccuracy)),
+ case sh_roll:percentage() of
+ X when (X =< MissChance) -> misses;
+ X when (X =< (MissChance * 2)) -> grazes;
+ _ -> hits
+ end.
+
+-spec roll_damage
+ (
+ sh_statistics:type(),
+ sh_statistics:type()
+ )
+ -> {non_neg_integer(), boolean()}.
+roll_damage (AttackerStatistics, _DefenderStatistics) ->
+ {MinimumDamage, MaximumDamage} =
+ sh_statistics:get_damages(AttackerStatistics),
+ MaximumRoll = max(1, MaximumDamage - MinimumDamage),
+ BaseDamage = MinimumDamage + (rand:uniform(MaximumRoll) - 1),
+ CriticalHitChance = sh_statistics:get_critical_hits(AttackerStatistics),
+ case sh_roll:percentage() of
+ X when (X =< CriticalHitChance) -> {(BaseDamage * 2), true};
+ _ -> {BaseDamage, false}
+ end.
+
+-spec roll_parry (sh_statistics:type()) -> boolean().
+roll_parry (DefenderStatistics) ->
+ DefenderParryChance = sh_statistics:get_parries(DefenderStatistics),
+ (sh_roll:percentage() =< DefenderParryChance).
+
+-spec effect_of_attack
+ (
+ order(),
+ btl_character:type(),
+ btl_character:type(),
+ boolean()
+ )
+ -> type().
+effect_of_attack (Order, Attacker, Defender, CanParry) ->
+ AttackerStatistics = btl_character:get_statistics(Attacker),
+ DefenderStatistics = btl_character:get_statistics(Defender),
+
+ ParryIsSuccessful = (CanParry and roll_parry(DefenderStatistics)),
+
+ {ActualAtkStatistics, ActualDefStatistics} =
+ case ParryIsSuccessful of
+ true -> {DefenderStatistics, AttackerStatistics};
+ false -> {AttackerStatistics, DefenderStatistics}
+ end,
+ {ActualAttacker, ActualDefender} =
+ case ParryIsSuccessful of
+ true -> {Defender, Attacker};
+ false -> {Attacker, Defender}
+ end,
+
+ ActualDefArmor = sh_armor:from_id(btl_character:get_armor_id(ActualDefender)),
+ {ActualAtkWeaponID, _} = btl_character:get_weapon_ids(ActualAttacker),
+ ActualAtkWeaponDmgType =
+ sh_weapon:get_damage_type(sh_weapon:from_id(ActualAtkWeaponID)),
+
+ Precision = roll_precision(ActualAtkStatistics, ActualDefStatistics),
+ {Damage, IsCritical} = roll_damage(ActualAtkStatistics, ActualDefStatistics),
+ S0Damage =
+ case Precision of
+ misses -> 0;
+ grazes -> trunc(Damage / 2);
+ hits -> Damage
+ end,
+ ArmorResistance =
+ sh_armor:get_resistance_to(ActualAtkWeaponDmgType, ActualDefArmor),
+ ActualDamage = max(0, (S0Damage - ArmorResistance)),
+
+ #attack
+ {
+ order = Order,
+ precision = Precision,
+ is_critical = IsCritical,
+ is_parry = ParryIsSuccessful,
+ damage = ActualDamage
+ }.
+
+-spec encode_order (order()) -> binary().
+encode_order (first) -> <<"f">>;
+encode_order (counter) -> <<"c">>;
+encode_order (second) -> <<"s">>.
+
+-spec encode_precision (precision()) -> binary().
+encode_precision (hits) -> <<"h">>;
+encode_precision (grazes) -> <<"g">>;
+encode_precision (misses) -> <<"m">>.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec get_description_of
+ (
+ step(),
+ btl_character:type(),
+ btl_character:type()
+ )
+ -> maybe_type().
+get_description_of ({first, CanParry}, Attacker, Defender) ->
+ effect_of_attack(first, Attacker, Defender, CanParry);
+get_description_of ({second, CanParry}, Attacker, Defender) ->
+ AttackerStatistics = btl_character:get_statistics(Attacker),
+ AttackerDoubleAttackChange =
+ sh_statistics:get_double_hits(AttackerStatistics),
+
+ case sh_roll:percentage() of
+ X when (X =< AttackerDoubleAttackChange) ->
+ effect_of_attack (second, Attacker, Defender, CanParry);
+
+ _ ->
+ nothing
+ end;
+get_description_of ({counter, CanParry}, Attacker, Defender) ->
+ effect_of_attack(counter, Defender, Attacker, CanParry).
+
+-spec apply_to_healths
+ (
+ maybe_type(),
+ non_neg_integer(),
+ non_neg_integer()
+ )
+ -> {maybe_type(), non_neg_integer(), non_neg_integer()}.
+apply_to_healths
+(
+ nothing,
+ AttackerHealth,
+ DefenderHealth
+) ->
+ {nothing, AttackerHealth, DefenderHealth};
+apply_to_healths
+(
+ _Attack,
+ AttackerHealth,
+ DefenderHealth
+)
+when
+(
+ (AttackerHealth =< 0)
+ or (DefenderHealth =< 0)
+) ->
+ {nothing, AttackerHealth, DefenderHealth};
+apply_to_healths
+(
+ Attack,
+ AttackerHealth,
+ DefenderHealth
+)
+when
+(
+ (
+ (not Attack#attack.is_parry)
+ and ((Attack#attack.order == first) or (Attack#attack.order == second))
+ )
+ or
+ (
+ Attack#attack.is_parry
+ and (Attack#attack.order == counter)
+ )
+) ->
+ Damage = Attack#attack.damage,
+
+ {
+ Attack,
+ AttackerHealth,
+ (DefenderHealth - Damage)
+ };
+apply_to_healths
+(
+ Attack,
+ AttackerHealth,
+ DefenderHealth
+)
+when
+(
+ (
+ (not Attack#attack.is_parry)
+ and (Attack#attack.order == counter)
+ )
+ or
+ (
+ Attack#attack.is_parry
+ and ((Attack#attack.order == first) or (Attack#attack.order == second))
+ )
+) ->
+ Damage = Attack#attack.damage,
+
+ {
+ Attack,
+ (AttackerHealth - Damage),
+ DefenderHealth
+ }.
+
+-spec get_sequence
+ (
+ non_neg_integer(),
+ sh_weapon:type(),
+ sh_weapon:type()
+ )
+ -> list(step()).
+get_sequence (AttackRange, AttackerWeapon, DefenderWeapon) ->
+ {AttackerDefenseRange, AttackerAttackRange} =
+ sh_weapon:get_ranges(AttackerWeapon),
+ {DefenderDefenseRange, DefenderAttackRange} =
+ sh_weapon:get_ranges(DefenderWeapon),
+
+ AttackerCanAttack = (AttackRange =< AttackerAttackRange),
+ AttackerCanAttack = true,
+ AttackerCanDefend =
+ (AttackerCanAttack and (AttackRange > AttackerDefenseRange)),
+ AttackerCanParry =
+ (AttackerCanDefend and sh_weapon:can_parry(AttackerWeapon)),
+
+ DefenderCanAttack = (AttackRange =< DefenderAttackRange),
+ DefenderCanDefend =
+ (DefenderCanAttack and (AttackRange > DefenderDefenseRange)),
+ DefenderCanParry =
+ (DefenderCanDefend and sh_weapon:can_parry(DefenderWeapon)),
+
+ First = {first, DefenderCanParry},
+ Second = {second, DefenderCanParry},
+ Counter = {counter, AttackerCanParry},
+
+ if
+ (not DefenderCanDefend) ->
+ [First, Second];
+
+ true ->
+ [First, Counter, Second]
+ end.
+
+-spec encode (type()) -> {list(any())}.
+encode (Attack) ->
+ Order = Attack#attack.order,
+ Precision = Attack#attack.precision,
+ IsCritical = Attack#attack.is_critical,
+ IsParry = Attack#attack.is_parry,
+ Damage = Attack#attack.damage,
+
+ {
+ [
+ {<<"ord">>, encode_order(Order)},
+ {<<"pre">>, encode_precision(Precision)},
+ {<<"cri">>, IsCritical},
+ {<<"par">>, IsParry},
+ {<<"dmg">>, Damage}
+ ]
+ }.
diff --git a/src/battle/struct/btl_battle.erl b/src/battle/struct/btl_battle.erl
new file mode 100644
index 0000000..8befc4e
--- /dev/null
+++ b/src/battle/struct/btl_battle.erl
@@ -0,0 +1,216 @@
+-module(btl_battle).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-type id() :: binary().
+
+-record
+(
+ battle,
+ {
+ id :: id(),
+ used_armor_ids :: list(sh_armor:id()),
+ used_weapon_ids :: list(sh_weapon:id()),
+ used_tile_ids :: list(btl_tile:id()),
+ battlemap :: btl_battlemap:type(),
+ characters :: array:array(btl_character:type()),
+ players :: array:array(btl_player:type()),
+ current_player_turn :: btl_player_turn:type()
+ }
+).
+
+-opaque type() :: #battle{}.
+
+-export_type([type/0, id/0]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%% Accessors
+-export
+(
+ [
+ get_id/1,
+ get_used_weapon_ids/1,
+ get_used_armor_ids/1,
+ get_used_tile_ids/1,
+ get_battlemap/1,
+ get_characters/1,
+ get_character/2,
+ get_players/1,
+ get_player/2,
+ get_current_player_turn/1,
+ get_encoded_last_turns_effects/1,
+
+ set_battlemap/2,
+ set_characters/2,
+ set_character/3,
+ set_players/2,
+ set_player/3,
+ set_current_player_turn/2,
+
+ get_characters_field/0,
+ get_players_field/0,
+ get_current_player_turn_field/0
+ ]
+).
+
+-export
+(
+ [
+ new/7
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+get_all_timelines (Result, CurrentIndex, EndPoint, ArraySize, Players) ->
+ Player = array:get(CurrentIndex, Players),
+ Timeline = btl_player:get_timeline(Player),
+ NextIndex = ((CurrentIndex + 1) rem ArraySize),
+ NextResult = (Timeline ++ Result),
+ case CurrentIndex of
+ EndPoint ->
+ NextResult;
+
+ _ ->
+ get_all_timelines(NextResult, NextIndex, EndPoint, ArraySize, Players)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%% Accessors
+-spec get_id (type()) -> id().
+get_id (Battle) -> Battle#battle.id.
+
+-spec get_used_weapon_ids (type()) -> list(sh_weapon:id()).
+get_used_weapon_ids (Battle) -> Battle#battle.used_weapon_ids.
+
+-spec get_used_armor_ids (type()) -> list(sh_armor:id()).
+get_used_armor_ids (Battle) -> Battle#battle.used_armor_ids.
+
+-spec get_used_tile_ids (type()) -> list(btl_tile:id()).
+get_used_tile_ids (Battle) -> Battle#battle.used_tile_ids.
+
+-spec get_battlemap (type()) -> btl_battlemap:type().
+get_battlemap (Battle) -> Battle#battle.battlemap.
+
+-spec get_characters (type()) -> array:array(btl_character:type()).
+get_characters (Battle) -> Battle#battle.characters.
+
+-spec get_character (non_neg_integer(), type()) -> btl_character:type().
+get_character (IX, Battle) ->
+ array:get(IX, Battle#battle.characters).
+
+-spec get_players (type()) -> array:array(btl_player:type()).
+get_players (Battle) ->
+ Battle#battle.players.
+
+-spec get_player (non_neg_integer(), type()) -> btl_player:type().
+get_player (IX, Battle) ->
+ array:get(IX, Battle#battle.players).
+
+-spec get_current_player_turn (type()) -> btl_player_turn:type().
+get_current_player_turn (Battle) ->
+ Battle#battle.current_player_turn.
+
+-spec get_encoded_last_turns_effects (type()) -> list(any()).
+get_encoded_last_turns_effects (Battle) ->
+ CurrentPlayerTurn = Battle#battle.current_player_turn,
+ Players = Battle#battle.players,
+ CurrentPlayerIX = btl_player_turn:get_player_ix(CurrentPlayerTurn),
+
+ PlayersCount = array:size(Players),
+ StartingPoint = ((CurrentPlayerIX + 1) rem PlayersCount),
+ get_all_timelines([], StartingPoint, CurrentPlayerIX, PlayersCount, Players).
+
+-spec set_battlemap (btl_battlemap:type(), type()) -> type().
+set_battlemap (Battlemap, Battle) ->
+ Battle#battle
+ {
+ battlemap = Battlemap
+ }.
+
+-spec set_characters (array:array(btl_character:type()), type()) -> type().
+set_characters (Characters, Battle) ->
+ Battle#battle
+ {
+ characters = Characters
+ }.
+
+-spec set_character (non_neg_integer(), btl_character:type(), type()) -> type().
+set_character (IX, Character, Battle) ->
+ Battle#battle
+ {
+ characters =
+ array:set
+ (
+ IX,
+ Character,
+ Battle#battle.characters
+ )
+ }.
+
+-spec set_players (array:array(btl_player:type()), type()) -> type().
+set_players (Players, Battle) ->
+ Battle#battle
+ {
+ players = Players
+ }.
+
+-spec set_player (non_neg_integer(), btl_player:type(), type()) -> type().
+set_player (IX, Player, Battle) ->
+ Battle#battle
+ {
+ players =
+ array:set
+ (
+ IX,
+ Player,
+ Battle#battle.players
+ )
+ }.
+
+-spec set_current_player_turn (btl_player_turn:type(), type()) -> type().
+set_current_player_turn (PlayerTurn, Battle) ->
+ Battle#battle
+ {
+ current_player_turn = PlayerTurn
+ }.
+
+-spec new
+ (
+ id(),
+ list(btl_player:type()),
+ btl_battlemap:type(),
+ list(btl_character:type()),
+ list(sh_weapon:id()),
+ list(sh_armor:id()),
+ list(btl_tile:id())
+ )
+ -> type().
+new (ID, PlayersAsList, Battlemap, CharactersAsList, UWIDs, UAIDs, UTIDs) ->
+ #battle
+ {
+ id = ID,
+ used_weapon_ids = UWIDs,
+ used_armor_ids = UAIDs,
+ used_tile_ids = UTIDs,
+ battlemap = Battlemap,
+ characters = array:from_list(CharactersAsList),
+ players = array:from_list(PlayersAsList),
+ current_player_turn = btl_player_turn:new(0, 0)
+ }.
+
+
+-spec get_characters_field () -> non_neg_integer().
+get_characters_field () -> #battle.characters.
+
+-spec get_players_field () -> non_neg_integer().
+get_players_field () -> #battle.players.
+
+-spec get_current_player_turn_field () -> non_neg_integer().
+get_current_player_turn_field () -> #battle.current_player_turn.
diff --git a/src/battle/struct/btl_battle_action.erl b/src/battle/struct/btl_battle_action.erl
new file mode 100644
index 0000000..307043a
--- /dev/null
+++ b/src/battle/struct/btl_battle_action.erl
@@ -0,0 +1,114 @@
+-module(btl_battle_action).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record
+(
+ move,
+ {
+ path :: list(btl_direction:enum())
+ }
+).
+
+-record
+(
+ switch_weapon,
+ {
+ }
+).
+
+-record
+(
+ attack,
+ {
+ target_ix :: non_neg_integer()
+ }
+).
+
+-type category() :: ('move' | 'switch_weapon' | 'attack' | 'nothing').
+-opaque type() :: (#move{} | #switch_weapon{} | #attack{}).
+
+-export_type([category/0, type/0]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ decode/1,
+ can_follow/2
+ ]
+).
+
+-export
+(
+ [
+ get_path/1,
+ get_target_ix/1,
+ get_category/1
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec decode_mov_action (map()) -> type().
+decode_mov_action (JSONMap) ->
+ PathInBinary = maps:get(<<"p">>, JSONMap),
+ Path = lists:map(fun btl_direction:decode/1, PathInBinary),
+
+ #move { path = Path }.
+
+-spec decode_atk_action (map()) -> type().
+decode_atk_action (JSONMap) ->
+ TargetIX = maps:get(<<"tix">>, JSONMap),
+
+ #attack { target_ix = TargetIX }.
+
+-spec decode_swp_action (map()) -> type().
+decode_swp_action (_JSONMap) ->
+ #switch_weapon{}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec decode (map()) -> type().
+decode (EncodedAction) ->
+ JSONActionMap = EncodedAction, %jiffy:decode(EncodedAction, [return_maps]),
+ ActionType = maps:get(<<"t">>, JSONActionMap),
+ case ActionType of
+ <<"mov">> -> decode_mov_action(JSONActionMap);
+ <<"atk">> -> decode_atk_action(JSONActionMap);
+ <<"swp">> -> decode_swp_action(JSONActionMap)
+ end.
+
+-spec can_follow (category(), category()) -> boolean().
+can_follow (nothing, attack) -> true;
+can_follow (nothing, switch_weapon) -> true;
+can_follow (nothing, move) -> true;
+can_follow (switch_weapon, attack) -> true;
+can_follow (move, attack) -> true;
+can_follow (_, _) -> false.
+
+-spec get_path (type()) -> list(btl_direction:type()).
+get_path (Action) when is_record(Action, move) ->
+ Action#move.path;
+get_path (_) ->
+ [].
+
+-spec get_target_ix (type()) -> non_neg_integer().
+get_target_ix (Action) when is_record(Action, attack) ->
+ Action#attack.target_ix;
+get_target_ix (_) ->
+ [].
+
+-spec get_category (type()) -> category().
+get_category (Action) when is_record(Action, attack) -> attack;
+get_category (Action) when is_record(Action, move) -> move;
+get_category (Action) when is_record(Action, switch_weapon) -> switch_weapon;
+get_category (Action) ->
+ io:format("How'd you get there?~p~n", [Action]),
+ true = Action.
+
diff --git a/src/battle/struct/btl_battlemap.erl b/src/battle/struct/btl_battlemap.erl
new file mode 100644
index 0000000..886e2a9
--- /dev/null
+++ b/src/battle/struct/btl_battlemap.erl
@@ -0,0 +1,100 @@
+-module(btl_battlemap).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-type id() :: binary().
+
+-record
+(
+ battlemap,
+ {
+ id :: id(),
+ width :: integer(),
+ height :: integer(),
+ tile_class_ids :: array:array(btl_tile:class_id())
+ }
+).
+
+-opaque type() :: #battlemap{}.
+
+-export_type([type/0, id/0]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%% Accessors
+-export
+(
+ [
+ get_id/1,
+ get_width/1,
+ get_height/1,
+ get_tile_class_ids/1,
+ get_tile_class_id/2
+ ]
+).
+
+-export
+(
+ [
+ from_list/4
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec location_to_array_index
+ (
+ non_neg_integer(),
+ btl_location:type()
+ )
+ -> ('error' | non_neg_integer()).
+location_to_array_index (ArrayWidth, {X, Y}) ->
+ if
+ (X < 0) -> error;
+ (Y < 0) -> error;
+ (X >= ArrayWidth) -> error;
+ true -> ((Y * ArrayWidth) + X)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%% Accessors
+-spec get_id (type()) -> id().
+get_id (Battlemap) -> Battlemap#battlemap.id.
+
+-spec get_width (type()) -> integer().
+get_width (Battlemap) -> Battlemap#battlemap.width.
+
+-spec get_height (type()) -> integer().
+get_height (Battlemap) -> Battlemap#battlemap.height.
+
+-spec get_tile_class_ids (type()) -> array:array(btl_tile:class_id()).
+get_tile_class_ids (Battlemap) -> Battlemap#battlemap.tile_class_ids.
+
+-spec get_tile_class_id (btl_location:type(), type()) -> btl_tile:class_id().
+get_tile_class_id (Location, Battlemap) ->
+ TileIX = location_to_array_index(Battlemap#battlemap.width, Location),
+ array:get(TileIX, Battlemap#battlemap.tile_class_ids).
+
+-spec from_list
+ (
+ non_neg_integer(),
+ non_neg_integer(),
+ non_neg_integer(),
+ list(non_neg_integer())
+ )
+ -> type().
+from_list (ID, Width, Height, List) ->
+ TileClassIDs = lists:map(fun btl_tile:class_id_from_int/1, List),
+
+ #battlemap
+ {
+ id = list_to_binary(integer_to_list(ID)),
+ width = Width,
+ height = Height,
+ tile_class_ids = array:from_list(TileClassIDs)
+ }.
diff --git a/src/battle/struct/btl_character.erl b/src/battle/struct/btl_character.erl
new file mode 100644
index 0000000..841f4ea
--- /dev/null
+++ b/src/battle/struct/btl_character.erl
@@ -0,0 +1,290 @@
+-module(btl_character).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-type id() :: non_neg_integer().
+-type rank() :: ('optional' | 'target' | 'commander').
+
+-record
+(
+ character,
+ {
+ id :: id(),
+ player_ix :: non_neg_integer(),
+ name :: binary(),
+ rank :: rank(),
+ icon :: binary(),
+ portrait :: binary(),
+ attributes :: sh_attributes:type(),
+ statistics :: sh_statistics:type(),
+ weapon_ids :: {sh_weapon:id(), sh_weapon:id()},
+ armor_id :: sh_armor:id(),
+ location :: {non_neg_integer(), non_neg_integer()},
+ current_health :: integer(), %% Negative integers let us reverse attacks.
+ is_active :: boolean(),
+ is_defeated :: boolean()
+ }
+).
+
+-opaque type() :: #character{}.
+
+-export_type([type/0, rank/0, id/0]).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%% Accessors
+-export
+(
+ [
+ get_id/1,
+ get_player_index/1,
+ get_name/1,
+ get_rank/1,
+ get_icon/1,
+ get_portrait/1,
+ get_attributes/1,
+ get_statistics/1,
+ get_weapon_ids/1,
+ get_armor_id/1,
+ get_location/1,
+ get_current_health/1,
+ get_is_alive/1,
+ get_is_active/1,
+ get_is_defeated/1,
+
+ set_rank/2,
+ set_weapon_ids/2,
+ set_armor_id/2,
+ set_statistics/2,
+ set_location/2,
+ set_current_health/2,
+ set_is_active/2,
+ set_is_defeated/2,
+
+ get_rank_field/0,
+ get_statistics_field/0,
+ get_weapons_field/0,
+ get_location_field/0,
+ get_current_health_field/0,
+ get_is_active_field/0,
+ get_is_defeated_field/0
+ ]
+).
+
+-export
+(
+ [
+ random/5
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec find_random_location
+ (
+ non_neg_integer(),
+ non_neg_integer(),
+ list({non_neg_integer(), non_neg_integer()})
+ )
+ -> {non_neg_integer(), non_neg_integer()}.
+find_random_location (BattlemapWidth, BattlemapHeight, ForbiddenLocations) ->
+ X = sh_roll:between(0, (BattlemapWidth - 1)),
+ Y = sh_roll:between(0, (BattlemapHeight - 1)),
+
+ IsForbidden = lists:member({X, Y}, ForbiddenLocations),
+
+ case IsForbidden of
+ true ->
+ find_random_location
+ (
+ BattlemapWidth,
+ BattlemapHeight,
+ ForbiddenLocations
+ );
+
+ _ -> {X, Y}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%% Accessors
+-spec get_id (type()) -> id().
+get_id (Char) -> Char#character.id.
+
+-spec get_player_index (type()) -> non_neg_integer().
+get_player_index (Char) -> Char#character.player_ix.
+
+-spec get_name (type()) -> binary().
+get_name (Char) -> Char#character.name.
+
+-spec get_rank (type()) -> rank().
+get_rank (Char) -> Char#character.rank.
+
+-spec get_icon (type()) -> binary().
+get_icon (Char) -> Char#character.icon.
+
+-spec get_portrait (type()) -> binary().
+get_portrait (Char) -> Char#character.portrait.
+
+-spec get_attributes (type()) -> sh_attributes:type().
+get_attributes (Char) -> Char#character.attributes.
+
+-spec get_armor_id (type()) -> sh_armor:id().
+get_armor_id (Char) -> Char#character.armor_id.
+
+-spec get_weapon_ids (type()) -> {sh_weapon:id(), sh_weapon:id()}.
+get_weapon_ids (Char) -> Char#character.weapon_ids.
+
+-spec get_statistics (type()) -> sh_statistics:type().
+get_statistics (Char) -> Char#character.statistics.
+
+-spec get_location (type()) -> {non_neg_integer(), non_neg_integer()}.
+get_location (Char) -> Char#character.location.
+
+-spec get_current_health (type()) -> integer().
+get_current_health (Char) -> Char#character.current_health.
+
+-spec get_is_alive (type()) -> boolean().
+get_is_alive (Char) ->
+ (
+ (not Char#character.is_defeated)
+ and (Char#character.current_health > 0)
+ ).
+
+-spec get_is_active (type()) -> boolean().
+get_is_active (Char) ->
+ (
+ (not Char#character.is_defeated)
+ and Char#character.is_active
+ and get_is_alive(Char)
+ ).
+
+-spec get_is_defeated (type()) -> boolean().
+get_is_defeated (Char) -> Char#character.is_defeated.
+
+-spec set_rank (rank(), type()) -> type().
+set_rank (Rank, Char) ->
+ Char#character
+ {
+ rank = Rank
+ }.
+
+-spec set_location
+ (
+ {non_neg_integer(), non_neg_integer()},
+ type()
+ )
+ -> type().
+set_location (Location, Char) ->
+ Char#character
+ {
+ location = Location
+ }.
+
+-spec set_current_health (integer(), type()) -> type().
+set_current_health (Health, Char) ->
+ Char#character
+ {
+ current_health = Health
+ }.
+
+-spec set_is_active (boolean(), type()) -> type().
+set_is_active (Active, Char) ->
+ Char#character
+ {
+ is_active = Active
+ }.
+
+-spec set_is_defeated (boolean(), type()) -> type().
+set_is_defeated (Defeated, Char) ->
+ Char#character
+ {
+ is_defeated = Defeated
+ }.
+
+-spec set_armor_id (sh_armor:id(), type()) -> type().
+set_armor_id (ArmorID, Char) ->
+ Char#character
+ {
+ armor_id = ArmorID
+ }.
+
+-spec set_weapon_ids ({sh_weapon:id(), sh_weapon:id()}, type()) -> type().
+set_weapon_ids (WeaponIDs, Char) ->
+ Char#character
+ {
+ weapon_ids = WeaponIDs
+ }.
+
+-spec set_statistics
+ (
+ sh_statistics:type(),
+ type()
+ )
+ -> type().
+set_statistics (Stats, Char) ->
+ Char#character
+ {
+ statistics = Stats
+ }.
+
+%%%% Utils
+-spec random
+ (
+ non_neg_integer(),
+ non_neg_integer(),
+ non_neg_integer(),
+ non_neg_integer(),
+ list({non_neg_integer(), non_neg_integer()})
+ )
+ -> type().
+random (ID, PlayerIX, BattlemapWidth, BattlemapHeight, ForbiddenLocations) ->
+ Location =
+ find_random_location(BattlemapWidth, BattlemapHeight, ForbiddenLocations),
+ WeaponIDs = {sh_weapon:random_id(), sh_weapon:random_id()},
+ ArmorID = sh_armor:random_id(),
+ Attributes = sh_attributes:random(),
+ Statistics = sh_statistics:new(Attributes, WeaponIDs, ArmorID),
+ IDAsListString = integer_to_list(ID),
+ IDAsBinaryString = list_to_binary(IDAsListString),
+
+ #character
+ {
+ id = ID,
+ player_ix = PlayerIX,
+ name = list_to_binary("Char" ++ IDAsListString),
+ rank =
+ if
+ ((ID rem 8) == 0) -> commander;
+ ((ID rem 3) == 0) -> target;
+ true -> optional
+ end,
+ icon = IDAsBinaryString,
+ portrait = IDAsBinaryString,
+ attributes = Attributes,
+ weapon_ids = WeaponIDs,
+ armor_id = ArmorID,
+ statistics = Statistics,
+ location = Location,
+ current_health = sh_statistics:get_health(Statistics),
+ is_active = false,
+ is_defeated = false
+ }.
+
+-spec get_rank_field() -> non_neg_integer().
+get_rank_field () -> #character.rank.
+-spec get_statistics_field() -> non_neg_integer().
+get_statistics_field () -> #character.statistics.
+-spec get_weapons_field() -> non_neg_integer().
+get_weapons_field () -> #character.weapon_ids.
+-spec get_location_field() -> non_neg_integer().
+get_location_field () -> #character.location.
+-spec get_current_health_field() -> non_neg_integer().
+get_current_health_field () -> #character.current_health.
+-spec get_is_active_field() -> non_neg_integer().
+get_is_active_field () -> #character.is_active.
+-spec get_is_defeated_field() -> non_neg_integer().
+get_is_defeated_field () -> #character.is_defeated.
diff --git a/src/battle/struct/btl_character_turn_data.erl b/src/battle/struct/btl_character_turn_data.erl
new file mode 100644
index 0000000..31a4b7d
--- /dev/null
+++ b/src/battle/struct/btl_character_turn_data.erl
@@ -0,0 +1,115 @@
+-module(btl_character_turn_data).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record
+(
+ type,
+ {
+ dirty :: boolean(),
+ battle :: btl_battle:type(),
+ character :: btl_character:type(),
+ character_ix :: non_neg_integer()
+ }
+).
+
+-opaque type() :: #type{}.
+
+-export_type([type/0]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ new/2,
+
+ get_battle_is_dirty/1,
+ get_battle/1,
+ get_character/1,
+ get_character_ix/1,
+
+ set_battle/2,
+ set_character/2
+ ]
+).
+
+-export
+(
+ [
+ clean_battle/1,
+ refresh_character/1
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec new (btl_battle:type(), non_neg_integer()) -> type().
+new (Battle, CharacterIX) ->
+ Character = btl_battle:get_character(CharacterIX, Battle),
+
+ #type
+ {
+ dirty = false,
+ battle = Battle,
+ character = Character,
+ character_ix = CharacterIX
+ }.
+
+-spec get_battle_is_dirty (type()) -> boolean().
+get_battle_is_dirty (Data) -> Data#type.dirty.
+
+-spec get_battle (type()) -> btl_battle:type().
+get_battle (Data) -> Data#type.battle.
+
+-spec get_character (type()) -> btl_character:type().
+get_character (Data) -> Data#type.character.
+
+-spec get_character_ix (type()) -> non_neg_integer().
+get_character_ix (Data) -> Data#type.character_ix.
+
+-spec set_battle (btl_battle:type(), type()) -> type().
+set_battle (Battle, Data) ->
+ Data#type{ battle = Battle }.
+
+-spec set_character (btl_character:type(), type()) -> type().
+set_character (Character, Data) ->
+ Data#type
+ {
+ dirty = true,
+ character = Character
+ }.
+
+-spec clean_battle (type()) -> type().
+clean_battle (Data) ->
+ Data#type
+ {
+ dirty = false,
+ battle =
+ btl_battle:set_character
+ (
+ Data#type.character_ix,
+ Data#type.character,
+ Data#type.battle
+ )
+ }.
+
+-spec refresh_character (type()) -> type().
+refresh_character (Data) ->
+ Data#type
+ {
+ dirty = false,
+ character =
+ btl_battle:get_character
+ (
+ Data#type.character_ix,
+ Data#type.battle
+ )
+ }.
diff --git a/src/battle/struct/btl_character_turn_request.erl b/src/battle/struct/btl_character_turn_request.erl
new file mode 100644
index 0000000..a4f310d
--- /dev/null
+++ b/src/battle/struct/btl_character_turn_request.erl
@@ -0,0 +1,84 @@
+-module(btl_character_turn_request).
+
+-define(PLAYER_ID_FIELD, <<"pid">>).
+-define(SESSION_TOKEN_FIELD, <<"stk">>).
+-define(BATTLE_ID_FIELD, <<"bid">>).
+-define(CHAR_IX_FIELD, <<"cix">>).
+-define(ACTIONS_FIELD, <<"act">>).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record
+(
+ type,
+ {
+ player_id :: btl_player:id(),
+ session_token :: binary(),
+ battle_id :: binary(),
+ character_ix :: non_neg_integer(),
+ actions :: list(btl_battle_action:type())
+ }
+).
+
+-opaque type() :: #type{}.
+
+-export_type([type/0]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ decode/1
+ ]
+).
+
+-export
+(
+ [
+ get_player_id/1,
+ get_session_token/1,
+ get_battle_id/1,
+ get_character_ix/1,
+ get_actions/1
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec decode (map()) -> type().
+decode (Map) ->
+ CharacterIX = maps:get(?CHAR_IX_FIELD, Map),
+ EncodedActions = maps:get(?ACTIONS_FIELD, Map),
+ Actions = lists:map(fun btl_battle_action:decode/1, EncodedActions),
+
+ #type
+ {
+ player_id = maps:get(?PLAYER_ID_FIELD, Map),
+ session_token = maps:get(?SESSION_TOKEN_FIELD, Map),
+ battle_id = maps:get(?BATTLE_ID_FIELD, Map),
+ character_ix = CharacterIX,
+ actions = Actions
+ }.
+
+-spec get_player_id (type()) -> btl_player:id().
+get_player_id (Request) -> Request#type.player_id.
+
+-spec get_session_token (type()) -> binary().
+get_session_token (Request) -> Request#type.session_token.
+
+-spec get_battle_id (type()) -> binary().
+get_battle_id (Request) -> Request#type.battle_id.
+
+-spec get_character_ix (type()) -> non_neg_integer().
+get_character_ix (Request) -> Request#type.character_ix.
+
+-spec get_actions (type()) -> list(btl_battle_action:type()).
+get_actions (Request) -> Request#type.actions.
diff --git a/src/battle/struct/btl_character_turn_update.erl b/src/battle/struct/btl_character_turn_update.erl
new file mode 100644
index 0000000..a6b29d9
--- /dev/null
+++ b/src/battle/struct/btl_character_turn_update.erl
@@ -0,0 +1,85 @@
+-module(btl_character_turn_update).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record
+(
+ type,
+ {
+ data :: btl_character_turn_data:type(),
+ timeline :: list(any()),
+ db :: list(sh_db_query:op())
+ }
+).
+
+-opaque type() :: #type{}.
+
+-export_type([type/0]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ new/1,
+
+ get_data/1,
+ get_timeline/1,
+ get_db/1,
+
+ set_data/2,
+ add_to_timeline/3,
+ add_to_db/2
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec new (btl_character_turn_data:type()) -> type().
+new (Data) ->
+ #type
+ {
+ data = Data,
+ timeline = [],
+ db = []
+ }.
+
+-spec get_data (type()) -> btl_character_turn_data:type().
+get_data (Update) -> Update#type.data.
+
+-spec get_timeline (type()) -> list(any()).
+get_timeline (Update) -> Update#type.timeline.
+
+-spec get_db (type()) -> list(sh_db_query:op()).
+get_db (Update) -> Update#type.db.
+
+-spec set_data (btl_character_turn_data:type(), type()) -> type().
+set_data (Data, Update) ->
+ Update#type{ data = Data}.
+
+-spec add_to_timeline
+ (
+ btl_turn_result:type(),
+ sh_db_query:op(),
+ type()
+ ) -> type().
+add_to_timeline (Item, DBUpdate, Update) ->
+ add_to_db
+ (
+ DBUpdate,
+ Update#type
+ {
+ timeline = [btl_turn_result:encode(Item)|Update#type.timeline]
+ }
+ ).
+
+-spec add_to_db (sh_db_query:op(), type()) -> type().
+add_to_db (Item, Update) ->
+ Update#type{ db = [Item|Update#type.db] }.
diff --git a/src/battle/struct/btl_direction.erl b/src/battle/struct/btl_direction.erl
new file mode 100644
index 0000000..9fb5a01
--- /dev/null
+++ b/src/battle/struct/btl_direction.erl
@@ -0,0 +1,38 @@
+-module(btl_direction).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-type enum() :: ('up' | 'down' | 'left' | 'right').
+-type type() :: enum().
+
+-export_type([enum/0, type/0]).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ decode/1,
+ encode/1
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec decode (binary()) -> enum().
+decode (<<"U">>) -> up;
+decode (<<"D">>) -> down;
+decode (<<"L">>) -> left;
+decode (<<"R">>) -> right.
+
+-spec encode (enum()) -> binary().
+encode (up) -> <<"U">>;
+encode (down) -> <<"D">>;
+encode (left) -> <<"L">>;
+encode (right) -> <<"R">>.
diff --git a/src/battle/struct/btl_location.erl b/src/battle/struct/btl_location.erl
new file mode 100644
index 0000000..9670cb0
--- /dev/null
+++ b/src/battle/struct/btl_location.erl
@@ -0,0 +1,90 @@
+-module(btl_location).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-type type() :: ({non_neg_integer(), non_neg_integer()} | 'nowhere').
+
+-export_type([type/0]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ decode/1,
+ encode/1,
+ get_nowhere/0
+ ]
+).
+
+-export
+(
+ [
+ apply_direction/2,
+ dist/2
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec validate ({integer(), integer()}) -> type().
+validate ({X, Y}) ->
+ if
+ (X < 0) -> nowhere;
+ (Y < 0) -> nowhere;
+ true -> {X, Y}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec get_nowhere () -> type().
+get_nowhere () -> nowhere.
+
+-spec apply_direction (btl_direction:enum(), type()) -> type().
+apply_direction (left, {X, Y}) ->
+ validate({(X - 1), Y});
+apply_direction (right, {X, Y}) ->
+ validate({(X + 1), Y});
+apply_direction (up, {X, Y}) ->
+ validate({X, (Y - 1)});
+apply_direction (down, {X, Y}) ->
+ validate({X, (Y + 1)});
+apply_direction (_, nowhere) ->
+ error("Trying to move from 'nowhere'."),
+ nowhere.
+
+-spec dist(type(), type()) -> non_neg_integer().
+dist ({OX, OY}, {DX, DY}) ->
+ (abs(DY - OY) + abs(DX - OX));
+dist (_, _) ->
+ error("Trying to measure distance to 'nowhere'"),
+ 999.
+
+-spec encode (type()) -> {list(any())}.
+encode ({X, Y}) ->
+ {
+ [
+ {<<"x">>, X},
+ {<<"y">>, Y}
+ ]
+ };
+encode (nowhere) ->
+ {
+ [
+ {<<"x">>, -1},
+ {<<"y">>, -1}
+ ]
+ }.
+
+-spec decode (map()) -> type().
+decode (Map) ->
+ X = maps:get(<<"x">>, Map),
+ Y = maps:get(<<"y">>, Map),
+
+ true = (is_integer(X) and is_integer(Y)),
+
+ validate({X, Y}).
diff --git a/src/battle/struct/btl_player.erl b/src/battle/struct/btl_player.erl
new file mode 100644
index 0000000..1cb1d93
--- /dev/null
+++ b/src/battle/struct/btl_player.erl
@@ -0,0 +1,104 @@
+-module(btl_player).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-type id() :: binary().
+
+-record
+(
+ player,
+ {
+ ix :: non_neg_integer(),
+ id :: id(),
+ character_ix :: non_neg_integer(),
+ timeline :: list(any()),
+ is_active :: boolean()
+ }
+).
+
+-opaque type() :: #player{}.
+
+-export_type([type/0, id/0]).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ get_id/1,
+ get_index/1,
+ get_character_index/1,
+ get_timeline/1,
+
+ get_is_active/1,
+ set_is_active/2,
+
+ add_to_timeline/2,
+ reset_timeline/1,
+
+ get_timeline_field/0,
+ get_is_active_field/0
+ ]
+).
+
+-export
+(
+ [
+ new/3
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec get_id (type()) -> id().
+get_id (Player) -> Player#player.id.
+
+-spec get_index (type()) -> non_neg_integer().
+get_index (Player) -> Player#player.ix.
+
+-spec get_character_index (type()) -> non_neg_integer().
+get_character_index (Player) -> Player#player.character_ix.
+
+-spec get_timeline (type()) -> list(any()).
+get_timeline (Player) -> Player#player.timeline.
+
+-spec get_is_active (type()) -> boolean().
+get_is_active (Player) -> Player#player.is_active.
+
+-spec set_is_active (boolean(), type()) -> type().
+set_is_active (Val, Player) -> Player#player{ is_active = Val }.
+
+-spec add_to_timeline (list(any()), type()) -> type().
+add_to_timeline (NewEvents, Player) ->
+ OldTimeline = Player#player.timeline,
+
+ Player#player
+ {
+ timeline = (NewEvents ++ OldTimeline)
+ }.
+
+-spec reset_timeline (type()) -> type().
+reset_timeline (Player) -> Player#player{ timeline = [] }.
+
+-spec new (non_neg_integer(), non_neg_integer(), id()) -> type().
+new (IX, CharacterIX, ID) ->
+ #player
+ {
+ ix = IX,
+ character_ix = CharacterIX,
+ id = ID,
+ is_active = true,
+ timeline = []
+ }.
+
+-spec get_timeline_field () -> non_neg_integer().
+get_timeline_field () -> #player.timeline.
+
+-spec get_is_active_field () -> non_neg_integer().
+get_is_active_field () -> #player.is_active.
diff --git a/src/battle/struct/btl_player_turn.erl b/src/battle/struct/btl_player_turn.erl
new file mode 100644
index 0000000..e0665f4
--- /dev/null
+++ b/src/battle/struct/btl_player_turn.erl
@@ -0,0 +1,106 @@
+-module(btl_player_turn).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record
+(
+ player_turn,
+ {
+ number :: non_neg_integer(),
+ player_ix :: non_neg_integer()
+ }
+).
+
+-opaque type() :: #player_turn{}.
+
+-export_type([type/0]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ new/2,
+ next/2
+ ]
+).
+
+%%%% Accessors
+-export
+(
+ [
+ get_number/1,
+ get_player_ix/1
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec next_valid_player
+ (
+ non_neg_integer(),
+ array:array(btl_player:type()),
+ non_neg_integer(),
+ non_neg_integer()
+ ) -> non_neg_integer().
+next_valid_player (StartingPoint, _Players, _PlayersCount, StartingPoint) ->
+ StartingPoint;
+next_valid_player (CandidateIX, Players, PlayersCount, StartingPoint) ->
+ Candidate = array:get(CandidateIX, Players),
+
+ case btl_player:get_is_active(Candidate) of
+ true -> CandidateIX;
+ _ ->
+ next_valid_player
+ (
+ ((CandidateIX + 1) rem PlayersCount),
+ Players,
+ PlayersCount,
+ StartingPoint
+ )
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%% Accessors
+-spec new (non_neg_integer(), non_neg_integer()) -> type().
+new (Number, PlayerIX) ->
+ #player_turn
+ {
+ number = Number,
+ player_ix = PlayerIX
+ }.
+
+-spec get_number (type()) -> non_neg_integer().
+get_number (PlayerTurn) -> PlayerTurn#player_turn.number.
+
+-spec get_player_ix (type()) -> non_neg_integer().
+get_player_ix (PlayerTurn) -> PlayerTurn#player_turn.player_ix.
+
+-spec next (array:array(btl_player:type()), type()) -> type().
+next (Players, CurrentPlayerTurn) ->
+ CurrentPlayerIX = CurrentPlayerTurn#player_turn.player_ix,
+ CurrentTurnNumber = CurrentPlayerTurn#player_turn.number,
+ PlayersCount = array:size(Players),
+
+ NextPlayerIX =
+ next_valid_player
+ (
+ ((CurrentPlayerIX + 1) rem PlayersCount),
+ Players,
+ PlayersCount,
+ CurrentPlayerIX
+ ),
+
+ NextTurnNumber =
+ case (NextPlayerIX < CurrentPlayerIX) of
+ true -> (CurrentTurnNumber + 1);
+ _ -> CurrentTurnNumber
+ end,
+
+ new(NextTurnNumber, NextPlayerIX).
diff --git a/src/battle/struct/btl_tile.erl b/src/battle/struct/btl_tile.erl
new file mode 100644
index 0000000..16e671b
--- /dev/null
+++ b/src/battle/struct/btl_tile.erl
@@ -0,0 +1,124 @@
+-module(btl_tile).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record
+(
+ tile,
+ {
+ id :: id(),
+ name :: binary(),
+ cost :: non_neg_integer(),
+ class_range_min :: non_neg_integer(),
+ class_range_max :: non_neg_integer()
+ }
+).
+
+-opaque id() :: non_neg_integer().
+-opaque class_id() :: non_neg_integer().
+-opaque type() :: #tile{}.
+
+-export_type([type/0, class_id/0, id/0]).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ get_id/1,
+ get_name/1,
+ get_cost/1,
+ get_range_minimum/1,
+ get_range_maximum/1,
+ from_id/1,
+ cost_when_oob/0
+ ]
+).
+
+-export
+(
+ [
+ class_id_to_type_id/1,
+ class_id_from_int/1
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec class_id_to_type_id (class_id()) -> id().
+class_id_to_type_id (ClassID) ->
+ case ClassID of
+ 0 -> 0;
+ 1 -> 1;
+ 2 -> 2;
+ N when ((N >= 3) and (N =< 17)) -> 3
+ end.
+
+-spec from_id (id()) -> type().
+from_id (0) ->
+ #tile
+ {
+ id = 0,
+ name = <<"[Grassland] Grass">>,
+ cost = 6,
+ class_range_min = 0,
+ class_range_max = 0
+ };
+from_id (1) ->
+ #tile
+ {
+ id = 1,
+ name = <<"[Grassland] Mushroom Infestation">>,
+ cost = 12,
+ class_range_min = 1,
+ class_range_max = 1
+ };
+from_id (2) ->
+ #tile
+ {
+ id = 2,
+ name = <<"[Grassland] Tree Remains">>,
+ cost = 24,
+ class_range_min = 2,
+ class_range_max = 2
+ };
+from_id (3) ->
+ #tile
+ {
+ id = 3,
+ name = <<"[Grassland] Clear Water">>,
+ cost = cost_when_occupied(),
+ class_range_min = 3,
+ class_range_max = 17
+ }.
+
+-spec cost_when_oob () -> non_neg_integer().
+cost_when_oob () -> 255.
+
+-spec cost_when_occupied () -> non_neg_integer().
+cost_when_occupied () -> 201.
+
+-spec get_id (type()) -> non_neg_integer().
+get_id (Tile) -> Tile#tile.id.
+
+-spec get_cost (type()) -> non_neg_integer().
+get_cost (Tile) -> Tile#tile.cost.
+
+-spec get_name (type()) -> binary().
+get_name (Tile) -> Tile#tile.name.
+
+-spec get_range_minimum (type()) -> non_neg_integer().
+get_range_minimum (Tile) -> Tile#tile.class_range_min.
+
+-spec get_range_maximum (type()) -> non_neg_integer().
+get_range_maximum (Tile) -> Tile#tile.class_range_max.
+
+-spec class_id_from_int (non_neg_integer()) -> id().
+class_id_from_int (I) -> I.
diff --git a/src/battle/struct/btl_turn_result.erl b/src/battle/struct/btl_turn_result.erl
new file mode 100644
index 0000000..97169e3
--- /dev/null
+++ b/src/battle/struct/btl_turn_result.erl
@@ -0,0 +1,215 @@
+-module(btl_turn_result).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+-record
+(
+ switched_weapon,
+ {
+ character_ix :: btl_character:id()
+ }
+).
+
+-record
+(
+ moved,
+ {
+ character_ix :: btl_character:id(),
+ path :: list(btl_direction:enum()),
+ new_location :: btl_location:type()
+ }
+).
+
+-record
+(
+ attacked,
+ {
+ attacker_ix :: btl_character:id(),
+ defender_ix :: btl_character:id(),
+ sequence :: list(btl_attack:type())
+ }
+).
+
+-record
+(
+ player_won,
+ {
+ player_ix :: non_neg_integer()
+ }
+).
+
+-record
+(
+ player_lost,
+ {
+ player_ix :: non_neg_integer()
+ }
+).
+
+-record
+(
+ player_turn_started,
+ {
+ player_ix :: non_neg_integer()
+ }
+).
+
+-opaque type() :: (
+ #switched_weapon{}
+ | #moved{}
+ | #attacked{}
+ | #player_won{}
+ | #player_lost{}
+ | #player_turn_started{}
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export_type([type/0]).
+
+-export
+(
+ [
+ new_player_won/1,
+ new_player_lost/1,
+ new_player_turn_started/1,
+ new_character_switched_weapons/1,
+ new_character_moved/3,
+ new_character_attacked/3
+ ]
+).
+
+-export
+(
+ [
+ encode/1
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec new_player_won (non_neg_integer()) -> type().
+new_player_won (PlayerIX) ->
+ #player_won { player_ix = PlayerIX }.
+
+-spec new_player_lost (non_neg_integer()) -> type().
+new_player_lost (PlayerIX) ->
+ #player_lost { player_ix = PlayerIX }.
+
+-spec new_player_turn_started (non_neg_integer()) -> type().
+new_player_turn_started (PlayerIX) ->
+ #player_turn_started { player_ix = PlayerIX }.
+
+-spec new_character_switched_weapons (btl_character:id()) -> type().
+new_character_switched_weapons (CharacterIX) ->
+ #switched_weapon { character_ix = CharacterIX }.
+
+-spec new_character_moved
+ (
+ btl_character:id(),
+ list(btl_direction:enum()),
+ btl_location:type()
+ )
+ -> type().
+new_character_moved (CharacterIX, Path, NewLocation) ->
+ #moved
+ {
+ character_ix = CharacterIX,
+ path = Path,
+ new_location = NewLocation
+ }.
+
+-spec new_character_attacked
+ (
+ btl_character:id(),
+ btl_character:id(),
+ list(btl_attack:type())
+ )
+ -> type().
+new_character_attacked (AttackerIX, DefenderIX, AttackSequence) ->
+ #attacked
+ {
+ attacker_ix = AttackerIX,
+ defender_ix = DefenderIX,
+ sequence = AttackSequence
+ }.
+
+-spec encode (type()) -> {list(any())}.
+encode (TurnResult) when is_record(TurnResult, switched_weapon) ->
+ CharacterIX = TurnResult#switched_weapon.character_ix,
+
+ {
+ [
+ {<<"t">>, <<"swp">>},
+ {<<"ix">>, CharacterIX}
+ ]
+ };
+encode (TurnResult) when is_record(TurnResult, moved) ->
+ CharacterIX = TurnResult#moved.character_ix,
+ Path = TurnResult#moved.path,
+ NewLocation = TurnResult#moved.new_location,
+
+ EncodedPath = lists:map(fun btl_direction:encode/1, Path),
+ EncodedNewLocation = btl_location:encode(NewLocation),
+
+ {
+ [
+ {<<"t">>, <<"mv">>},
+ {<<"ix">>, CharacterIX},
+ {<<"p">>, EncodedPath},
+ {<<"nlc">>, EncodedNewLocation}
+ ]
+ };
+encode (TurnResult) when is_record(TurnResult, attacked) ->
+ AttackerIX = TurnResult#attacked.attacker_ix,
+ DefenderIX = TurnResult#attacked.defender_ix,
+ Sequence = TurnResult#attacked.sequence,
+
+ EncodedSequence = lists:map(fun btl_attack:encode/1, Sequence),
+
+ {
+ [
+ {<<"t">>, <<"atk">>},
+ {<<"aix">>, AttackerIX},
+ {<<"dix">>, DefenderIX},
+ {<<"seq">>, EncodedSequence}
+ ]
+ };
+encode (TurnResult) when is_record(TurnResult, player_won) ->
+ PlayerIX = TurnResult#player_won.player_ix,
+
+ {
+ [
+ {<<"t">>, <<"pwo">>},
+ {<<"ix">>, PlayerIX}
+ ]
+ };
+encode (TurnResult) when is_record(TurnResult, player_lost) ->
+ PlayerIX = TurnResult#player_lost.player_ix,
+
+ {
+ [
+ {<<"t">>, <<"plo">>},
+ {<<"ix">>, PlayerIX}
+ ]
+ };
+encode (TurnResult) when is_record(TurnResult, player_turn_started) ->
+ PlayerIX = TurnResult#player_turn_started.player_ix,
+
+ {
+ [
+ {<<"t">>, <<"pts">>},
+ {<<"ix">>, PlayerIX}
+ ]
+ };
+encode (Other) ->
+ io:format("~n invalid encode param\"~p\"~n", [Other]),
+ true = Other.