oakdex-battle 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a1d0972ff95da713b2117cf6f642ac45edc48daa
4
- data.tar.gz: 9d71621683079bebffd2703b7c448a3df79873ca
3
+ metadata.gz: 2066dbb4e78aff0e298029c4eb76f345326e172e
4
+ data.tar.gz: bf2201bd2ebfdb085b1fa2b766ec344e41c8a97c
5
5
  SHA512:
6
- metadata.gz: '08b23d0d4936817b5a93f044fd69af58ee74b534e973e97d143555d9f0f6b2af9b23a95c910833aa689bd13e794dcf4a37d383f09173906c4d632ffcd2613acd'
7
- data.tar.gz: 25f79543b12fde79d5777cc6857a65b08aaee38bca10a11fa9c32b2bb259b50634d2f01a6502221432d786c04edcc9bd86bfc673846b47921ffc5f3c39d07ee0
6
+ metadata.gz: 30127361db91d8748335a8a58609a045ddf3c7faceac02a034e8da966496c89271f9c4ebe2cd3e3af3e83b6e819b2904e6ae80b823d22501b28696cb3fb5e7d7
7
+ data.tar.gz: 61482125d2312a029a6de584efc8069be965e9c21b26cdf7163e3501e3273c7d519b8578b61284adb770a5d6570c7e4b320e94371d8126d769d1ba228ad2f4a0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # <img src="https://v20.imgup.net/oakdex_logfbad.png" alt="fixer" width=282>
2
2
 
3
- [![Build Status](https://travis-ci.org/jalyna/oakdex-battle.svg?branch=master)](https://travis-ci.org/jalyna/oakdex-battle) [![Maintainability](https://api.codeclimate.com/v1/badges/ef91681257a6900f03ac/maintainability)](https://codeclimate.com/github/jalyna/oakdex-battle/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/ef91681257a6900f03ac/test_coverage)](https://codeclimate.com/github/jalyna/oakdex-battle/test_coverage)
3
+ [![Gem Version](https://badge.fury.io/rb/oakdex-battle.svg)](https://badge.fury.io/rb/oakdex-battle) [![Build Status](https://travis-ci.org/jalyna/oakdex-battle.svg?branch=master)](https://travis-ci.org/jalyna/oakdex-battle) [![Maintainability](https://api.codeclimate.com/v1/badges/ef91681257a6900f03ac/maintainability)](https://codeclimate.com/github/jalyna/oakdex-battle/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/ef91681257a6900f03ac/test_coverage)](https://codeclimate.com/github/jalyna/oakdex-battle/test_coverage)
4
4
 
5
5
  Based on [oakdex-pokedex](https://github.com/jalyna/oakdex-pokedex).
6
6
 
@@ -11,8 +11,8 @@ Based on [oakdex-pokedex](https://github.com/jalyna/oakdex-pokedex).
11
11
  ```ruby
12
12
  require 'oakdex/battle'
13
13
 
14
- pok1 = Oakdex::Battle::Pokemon.create('Pikachu', level: 10)
15
- pok2 = Oakdex::Battle::Pokemon.create('Bulbasaur', {
14
+ pok1 = Oakdex::Pokemon.create('Pikachu', level: 10)
15
+ pok2 = Oakdex::Pokemon.create('Bulbasaur', {
16
16
  exp: 120,
17
17
  gender: 'female',
18
18
  ability: 'Soundproof',
@@ -75,8 +75,8 @@ battle.winner # => trainer1
75
75
  ### Other Battle types
76
76
 
77
77
  ```ruby
78
- pok3 = Oakdex::Battle::Pokemon.create('Altaria', level: 20)
79
- pok4 = Oakdex::Battle::Pokemon.create('Elekid', level: 14)
78
+ pok3 = Oakdex::Pokemon.create('Altaria', level: 20)
79
+ pok4 = Oakdex::Pokemon.create('Elekid', level: 14)
80
80
  trainer1 = Oakdex::Battle::Trainer.new('Ash', [pok1, pok3, pok9])
81
81
  trainer2 = Oakdex::Battle::Trainer.new('Misty', [pok2, pok4, pok10])
82
82
  trainer3 = Oakdex::Battle::Trainer.new('Brock', [pok5, pok6])
@@ -90,19 +90,6 @@ battle = Oakdex::Battle.new([trainer1], [trainer2], pokemon_per_side: 3)
90
90
  battle = Oakdex::Battle.new([trainer1, trainer3], [trainer2, trainer4])
91
91
  ```
92
92
 
93
-
94
- ### Breeding
95
-
96
- ```ruby
97
- pok1 = Oakdex::Battle::Pokemon.create('Ditto', level: 20)
98
- pok2 = Oakdex::Battle::Pokemon.create('Pikachu', level: 20, gender: 'female')
99
-
100
- Oakdex::Battle::Breeding.compatible?(pok1, pok2) # => true
101
- Oakdex::Battle::Breeding.chance_in_percentage(pok1, pok2) # => 20
102
- Oakdex::Battle::Breeding.breed(pok1, pok2) #=> Oakdex::Battle::Pokemon Pichu
103
- ```
104
-
105
-
106
93
  ## Contributing
107
94
 
108
95
  I would be happy if you want to add your contribution to the project. In order to contribute, you just have to fork this repository.
data/lib/oakdex/battle.rb CHANGED
@@ -1,6 +1,6 @@
1
- require 'oakdex/pokedex'
1
+ require 'oakdex/pokemon'
2
2
 
3
- require 'oakdex/battle/pokemon'
3
+ require 'oakdex/battle/in_battle_pokemon'
4
4
  require 'oakdex/battle/trainer'
5
5
  require 'oakdex/battle/move_execution'
6
6
  require 'oakdex/battle/action'
@@ -8,8 +8,7 @@ require 'oakdex/battle/damage'
8
8
  require 'oakdex/battle/turn'
9
9
  require 'oakdex/battle/valid_action_service'
10
10
  require 'oakdex/battle/side'
11
- require 'oakdex/battle/in_battle_pokemon'
12
- require 'oakdex/battle/breeding'
11
+ require 'oakdex/battle/active_in_battle_pokemon'
13
12
 
14
13
  module Oakdex
15
14
  # Represents battle, with has n turns and m sides
@@ -76,12 +76,12 @@ module Oakdex
76
76
  end
77
77
 
78
78
  def pokemon_by_position
79
- trainer.in_battle_pokemon
79
+ trainer.active_in_battle_pokemon
80
80
  .find { |ibp| ibp.position == @attributes[:pokemon] }&.pokemon
81
81
  end
82
82
 
83
83
  def target_by_position(side, position)
84
- side.in_battle_pokemon
84
+ side.active_in_battle_pokemon
85
85
  .find { |ibp| ibp.position == position }&.pokemon
86
86
  end
87
87
 
@@ -0,0 +1,152 @@
1
+ require 'forwardable'
2
+
3
+ module Oakdex
4
+ class Battle
5
+ # Represents a pokemon that is actively fighting in battle
6
+ class ActiveInBattlePokemon
7
+ extend Forwardable
8
+
9
+ def_delegators :@pokemon, :moves_with_pp, :fainted?
10
+ def_delegators :@side, :battle
11
+
12
+ attr_reader :pokemon, :position, :side
13
+
14
+ def initialize(pokemon, side, position = 0)
15
+ @pokemon = pokemon
16
+ @side = side
17
+ @position = position
18
+ end
19
+
20
+ def action_added?
21
+ actions.any? { |a| a.pokemon == pokemon }
22
+ end
23
+
24
+ def valid_move_actions
25
+ return [] if action_added?
26
+ moves = moves_with_pp
27
+ moves = [struggle_move] if moves_with_pp.empty?
28
+ moves.flat_map do |move|
29
+ targets_in_battle(move).map do |target|
30
+ {
31
+ action: 'move',
32
+ pokemon: pokemon,
33
+ move: move,
34
+ target: target
35
+ }
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def targets_in_battle(move)
43
+ available_targets(move).map do |targets|
44
+ if targets.last.is_a?(Array)
45
+ targets if targets_in_battle?(targets)
46
+ elsif target_in_battle?(targets)
47
+ targets
48
+ end
49
+ end.compact.reject(&:empty?)
50
+ end
51
+
52
+ def targets_in_battle?(targets)
53
+ targets.any? { |target| target[0].pokemon_in_battle?(target[1]) }
54
+ end
55
+
56
+ def target_in_battle?(target)
57
+ target[0].pokemon_in_battle?(target[1]) ||
58
+ (!target[0].pokemon_left? && target[1] == 0)
59
+ end
60
+
61
+ def struggle_move
62
+ @struggle_move ||= Oakdex::Pokemon::Move.create('Struggle')
63
+ end
64
+
65
+ def available_targets(move)
66
+ with_target(move) || multiple_targets_adjacent(move) ||
67
+ multiple_targets(move) || []
68
+ end
69
+
70
+ def multiple_targets(move)
71
+ case move.target
72
+ when 'all_users' then [all_users]
73
+ when 'all_except_user' then [all_targets - [self_target]]
74
+ when 'all' then [all_targets]
75
+ when 'all_foes' then [all_foes]
76
+ end
77
+ end
78
+
79
+ def multiple_targets_adjacent(move)
80
+ case move.target
81
+ when 'all_adjacent' then [adjacent]
82
+ when 'adjacent_foes_all' then [adjacent_foes]
83
+ end
84
+ end
85
+
86
+ def with_target(move)
87
+ case move.target
88
+ when 'user', 'user_and_random_adjacent_foe' then [self_target]
89
+ when 'target_adjacent_user_single' then adjacent_users
90
+ when 'target_adjacent_single' then adjacent
91
+ when 'target_user_or_adjacent_user'
92
+ [self_target] + adjacent_users
93
+ end
94
+ end
95
+
96
+ def all_targets
97
+ all_foes + all_users
98
+ end
99
+
100
+ def target_adjacent_single
101
+ adjacent_foes + adjacent_users
102
+ end
103
+
104
+ def adjacent
105
+ adjacent_foes + adjacent_users
106
+ end
107
+
108
+ def self_target
109
+ [@side, position]
110
+ end
111
+
112
+ def adjacent_foes
113
+ [
114
+ [other_side, position - 1],
115
+ [other_side, position],
116
+ [other_side, position + 1]
117
+ ].select { |t| t[1] >= 0 && t[1] < pokemon_per_side }
118
+ end
119
+
120
+ def adjacent_users
121
+ [
122
+ [@side, position - 1],
123
+ [@side, position + 1]
124
+ ].select { |t| t[1] >= 0 && t[1] < pokemon_per_side }
125
+ end
126
+
127
+ def all_users
128
+ pokemon_per_side.times.map { |i| [@side, i] }
129
+ end
130
+
131
+ def all_foes
132
+ pokemon_per_side.times.map { |i| [other_side, i] }
133
+ end
134
+
135
+ def pokemon_per_side
136
+ battle.pokemon_per_side
137
+ end
138
+
139
+ def other_side
140
+ other_sides.first
141
+ end
142
+
143
+ def other_sides
144
+ battle.sides - [@side]
145
+ end
146
+
147
+ def actions
148
+ battle.actions
149
+ end
150
+ end
151
+ end
152
+ end
@@ -65,13 +65,12 @@ module Oakdex
65
65
  end
66
66
 
67
67
  def stab_modifier
68
- pokemon.types.include?(move.type) ? 1.5 : 1.0
68
+ pokemon.types.include?(move.type_id) ? 1.5 : 1.0
69
69
  end
70
70
 
71
71
  def type_modifier
72
- type_data = Oakdex::Pokedex::Type.find!(move.type)
73
72
  target.types.reduce(1.0) do |factor, type|
74
- factor * type_data.effectivness[type]
73
+ factor * move.type.effectivness[type]
75
74
  end
76
75
  end
77
76
 
@@ -1,158 +1,151 @@
1
1
  require 'forwardable'
2
+ require 'oakdex/battle/status_conditions'
2
3
 
3
4
  module Oakdex
4
5
  class Battle
5
- # Represents a pokemon that is in battle
6
+ # Represents detailed pokemon instance that is part of a Trainer's Team
6
7
  class InBattlePokemon
7
8
  extend Forwardable
8
9
 
9
- def_delegators :@pokemon, :current_hp, :moves_with_pp
10
- def_delegators :@side, :battle
11
-
12
- attr_reader :pokemon, :position, :side
13
-
14
- def initialize(pokemon, side, position = 0)
10
+ OTHER_STATS = %i[accuracy evasion critical_hit]
11
+ ALL_STATS = Oakdex::Pokemon::BATTLE_STATS + OTHER_STATS
12
+ STATUS_CONDITIONS = {
13
+ 'poison' => StatusConditions::Poison,
14
+ 'burn' => StatusConditions::Burn,
15
+ 'freeze' => StatusConditions::Freeze,
16
+ 'paralysis' => StatusConditions::Paralysis,
17
+ 'badly_poisoned' => StatusConditions::BadlyPoisoned,
18
+ 'sleep' => StatusConditions::Sleep
19
+ }
20
+
21
+ STAGE_MULTIPLIERS = {
22
+ -6 => Rational(2, 8),
23
+ -5 => Rational(2, 7),
24
+ -4 => Rational(2, 6),
25
+ -3 => Rational(2, 5),
26
+ -2 => Rational(2, 4),
27
+ -1 => Rational(2, 3),
28
+ 0 => Rational(2, 2),
29
+ 1 => Rational(3, 2),
30
+ 2 => Rational(4, 2),
31
+ 3 => Rational(5, 2),
32
+ 4 => Rational(6, 2),
33
+ 5 => Rational(7, 2),
34
+ 6 => Rational(8, 2)
35
+ }
36
+
37
+ STAGE_MULTIPLIERS_CRITICAL_HIT = {
38
+ 0 => Rational(1, 24),
39
+ 1 => Rational(1, 8),
40
+ 2 => Rational(1, 2),
41
+ 3 => Rational(1, 1)
42
+ }
43
+
44
+ STAGE_MULTIPLIERS_ACC_EVA = {
45
+ -6 => Rational(3, 9),
46
+ -5 => Rational(3, 8),
47
+ -4 => Rational(3, 7),
48
+ -3 => Rational(3, 6),
49
+ -2 => Rational(3, 5),
50
+ -1 => Rational(3, 4),
51
+ 0 => Rational(3, 3),
52
+ 1 => Rational(4, 3),
53
+ 2 => Rational(5, 3),
54
+ 3 => Rational(6, 3),
55
+ 4 => Rational(7, 3),
56
+ 5 => Rational(8, 3),
57
+ 6 => Rational(9, 3)
58
+ }
59
+
60
+ def_delegators :@pokemon, :types, :trainer, :trainer=,
61
+ :name, :moves, :moves_with_pp, :change_hp_by,
62
+ :change_pp_by, :level, :fainted?
63
+
64
+ attr_reader :status_conditions
65
+
66
+ def initialize(pokemon, options = {})
15
67
  @pokemon = pokemon
16
- @side = side
17
- @position = position
18
- end
19
-
20
- def fainted?
21
- current_hp.zero?
22
- end
23
-
24
- def action_added?
25
- actions.any? { |a| a.pokemon == pokemon }
26
- end
27
-
28
- def valid_move_actions
29
- return [] if action_added?
30
- moves = moves_with_pp
31
- moves = [struggle_move] if moves_with_pp.empty?
32
- moves.flat_map do |move|
33
- targets_in_battle(move).map do |target|
34
- {
35
- action: 'move',
36
- pokemon: pokemon,
37
- move: move,
38
- target: target
39
- }
40
- end
68
+ @status_conditions = options[:status_conditions] || []
69
+ if @pokemon.primary_status_condition
70
+ add_status_condition(@pokemon.primary_status_condition)
41
71
  end
72
+ reset_stats
42
73
  end
43
74
 
44
- private
45
-
46
- def targets_in_battle(move)
47
- available_targets(move).map do |targets|
48
- if targets.last.is_a?(Array)
49
- targets if targets_in_battle?(targets)
50
- elsif target_in_battle?(targets)
51
- targets
52
- end
53
- end.compact.reject(&:empty?)
75
+ def change_stat_by(stat, change_by)
76
+ modifiers = stage_multipliers(stat)
77
+ stat_before = @stat_modifiers[stat]
78
+ min_value = modifiers.keys.first
79
+ max_value = modifiers.keys.last
80
+ @stat_modifiers[stat] = if change_by < 0
81
+ [stat_before + change_by, min_value].max
82
+ else
83
+ [stat_before + change_by, max_value].min
84
+ end
85
+ stat_before != @stat_modifiers[stat]
54
86
  end
55
87
 
56
- def targets_in_battle?(targets)
57
- targets.any? { |target| target[0].pokemon_in_battle?(target[1]) }
88
+ def add_status_condition(condition_name)
89
+ @status_conditions << status_condition(condition_name)
90
+ @pokemon.primary_status_condition = condition_name
58
91
  end
59
92
 
60
- def target_in_battle?(target)
61
- target[0].pokemon_in_battle?(target[1]) ||
62
- (!target[0].pokemon_left? && target[1] == 0)
93
+ def remove_status_condition(condition)
94
+ @status_conditions = @status_conditions.reject { |s| s == condition }
95
+ @pokemon.primary_status_condition = nil if @status_conditions.empty?
63
96
  end
64
97
 
65
- def struggle_move
66
- @struggle_move ||= begin
67
- move_type = Oakdex::Pokedex::Move.find('Struggle')
68
- Oakdex::Battle::Move.new(move_type, move_type.pp, move_type.pp)
69
- end
98
+ def reset_stats
99
+ @stat_modifiers = (ALL_STATS - %i[hp]).map do |stat|
100
+ [stat, 0]
101
+ end.to_h
70
102
  end
71
103
 
72
- def available_targets(move)
73
- with_target(move) || multiple_targets_adjacent(move) ||
74
- multiple_targets(move) || []
104
+ def accuracy
105
+ stage(:accuracy)
75
106
  end
76
107
 
77
- def multiple_targets(move)
78
- case move.target
79
- when 'all_users' then [all_users]
80
- when 'all_except_user' then [all_targets - [self_target]]
81
- when 'all' then [all_targets]
82
- when 'all_foes' then [all_foes]
83
- end
108
+ def evasion
109
+ stage(:evasion)
84
110
  end
85
111
 
86
- def multiple_targets_adjacent(move)
87
- case move.target
88
- when 'all_adjacent' then [adjacent]
89
- when 'adjacent_foes_all' then [adjacent_foes]
90
- end
112
+ def critical_hit_prob
113
+ stage(:critical_hit)
91
114
  end
92
115
 
93
- def with_target(move)
94
- case move.target
95
- when 'user', 'user_and_random_adjacent_foe' then [self_target]
96
- when 'target_adjacent_user_single' then adjacent_users
97
- when 'target_adjacent_single' then adjacent
98
- when 'target_user_or_adjacent_user'
99
- [self_target] + adjacent_users
116
+ Oakdex::Pokemon::BATTLE_STATS.each do |stat|
117
+ define_method stat do
118
+ (@pokemon.public_send(stat) * stage(stat) *
119
+ status_condition_modifier(stat)).to_i
100
120
  end
101
121
  end
102
122
 
103
- def all_targets
104
- all_foes + all_users
105
- end
106
-
107
- def target_adjacent_single
108
- adjacent_foes + adjacent_users
109
- end
110
-
111
- def adjacent
112
- adjacent_foes + adjacent_users
113
- end
114
-
115
- def self_target
116
- [@side, position]
117
- end
118
-
119
- def adjacent_foes
120
- [
121
- [other_side, position - 1],
122
- [other_side, position],
123
- [other_side, position + 1]
124
- ].select { |t| t[1] >= 0 && t[1] < pokemon_per_side }
125
- end
126
-
127
- def adjacent_users
128
- [
129
- [@side, position - 1],
130
- [@side, position + 1]
131
- ].select { |t| t[1] >= 0 && t[1] < pokemon_per_side }
132
- end
133
-
134
- def all_users
135
- pokemon_per_side.times.map { |i| [@side, i] }
136
- end
137
-
138
- def all_foes
139
- pokemon_per_side.times.map { |i| [other_side, i] }
140
- end
123
+ private
141
124
 
142
- def pokemon_per_side
143
- battle.pokemon_per_side
125
+ def status_condition_modifier(stat)
126
+ status_conditions.reduce(1.0) do |modifier, condition|
127
+ condition.stat_modifier(stat) * modifier
128
+ end
144
129
  end
145
130
 
146
- def other_side
147
- other_sides.first
131
+ def status_condition(condition_name)
132
+ STATUS_CONDITIONS[condition_name].new(self)
148
133
  end
149
134
 
150
- def other_sides
151
- battle.sides - [@side]
135
+ def stage(stat)
136
+ multipliers = stage_multipliers(stat)
137
+ multipliers[@stat_modifiers[stat] || 0]
152
138
  end
153
139
 
154
- def actions
155
- battle.actions
140
+ def stage_multipliers(stat)
141
+ case stat
142
+ when :evasion, :accuracy
143
+ STAGE_MULTIPLIERS_ACC_EVA
144
+ when :critical_hit
145
+ STAGE_MULTIPLIERS_CRITICAL_HIT
146
+ else
147
+ STAGE_MULTIPLIERS
148
+ end
156
149
  end
157
150
  end
158
151
  end