From 5052134adddb07d286a58c5b1fd7dcbe8335db98 Mon Sep 17 00:00:00 2001 From: Toby Date: Thu, 9 Jan 2025 00:50:56 +0100 Subject: [PATCH] first commit --- LICENCE | 291 +++++++++++++++++++++++++++++++++++++++ README.md | 5 + src/battle.rs | 120 +++++++++------- src/constants.rs | 3 + src/data/mod.rs | 88 +++++++++++- src/data/moves.rs | 47 ------- src/data/moves_db.json | 23 ++++ src/data/pokemon.rs | 44 ------ src/data/pokemon_db.json | 42 ++++++ src/main.rs | 30 ++-- src/moves.rs | 27 +--- src/pokemon.rs | 159 ++++++++++++++++----- src/trainer.rs | 75 ++++++++++ src/types.rs | 155 +++++++++++++-------- src/utils.rs | 7 + 15 files changed, 846 insertions(+), 270 deletions(-) create mode 100644 LICENCE create mode 100644 README.md create mode 100644 src/constants.rs delete mode 100644 src/data/moves.rs create mode 100644 src/data/moves_db.json delete mode 100644 src/data/pokemon.rs create mode 100644 src/data/pokemon_db.json create mode 100644 src/trainer.rs create mode 100644 src/utils.rs diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..22953c3 --- /dev/null +++ b/LICENCE @@ -0,0 +1,291 @@ + EUROPEAN UNION PUBLIC LICENCE v. 1.2 + EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as +defined below) which is provided under the terms of this Licence. Any use of +the Work, other than as authorised under this Licence is prohibited (to the +extent such use is covered by a right of the copyright holder of the Work). + +The Work is provided under the terms of this Licence when the Licensor (as +defined below) has placed the following notice immediately following the +copyright notice for the Work: + + Licensed under the EUPL + +or has expressed by any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: + +- ‘The Licence’: this Licence. + +- ‘The Original Work’: the work or software distributed or communicated by the + Licensor under this Licence, available as Source Code and also as Executable + Code as the case may be. + +- ‘Derivative Works’: the works or software that could be created by the + Licensee, based upon the Original Work or modifications thereof. This + Licence does not define the extent of modification or dependence on the + Original Work required in order to classify a work as a Derivative Work; + this extent is determined by copyright law applicable in the country + mentioned in Article 15. + +- ‘The Work’: the Original Work or its Derivative Works. + +- ‘The Source Code’: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- ‘The Executable Code’: any code which has generally been compiled and which + is meant to be interpreted by a computer as a program. + +- ‘The Licensor’: the natural or legal person that distributes or communicates + the Work under the Licence. + +- ‘Contributor(s)’: any natural or legal person who modifies the Work under + the Licence, or otherwise contributes to the creation of a Derivative Work. + +- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of + the Work under the terms of the Licence. + +- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, online or offline, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +sublicensable licence to do the following, for the duration of copyright +vested in the Original Work: + +- use the Work in any circumstance and for all usage, +- reproduce the Work, +- modify the Work, and make Derivative Works based upon the Work, +- communicate to the public, including the right to make available or display + the Work or copies thereof to the public and perform publicly, as the case + may be, the Work, +- distribute the Work or copies thereof, +- lend and rent the Work or copies thereof, +- sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make +effective the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights +to any patents held by the Licensor, to the extent necessary to make use of +the rights granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, +in a notice following the copyright notice attached to the Work, a repository +where the Source Code is easily and freely accessible for as long as the +Licensor continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits +from any exception or limitation to the exclusive rights of the rights owners +in the Work, of the exhaustion of those rights or of other applicable +limitations thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and +a copy of the Licence with every copy of the Work he/she distributes or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the +Original Works or Derivative Works, this Distribution or Communication will be +done under the terms of this Licence or of a later version of this Licence +unless the Original Work is expressly distributed only under this version of +the Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee +(becoming Licensor) cannot offer or impose any additional terms or conditions +on the Work or Derivative Work that alter or restrict the terms of the +Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative +Works or copies thereof based upon both the Work and another work licensed +under a Compatible Licence, this Distribution or Communication can be done +under the terms of this Compatible Licence. For the sake of this clause, +‘Compatible Licence’ refers to the licences listed in the appendix attached to +this Licence. Should the Licensee's obligations under the Compatible Licence +conflict with his/her obligations under this Licence, the obligations of the +Compatible Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the +Work, the Licensee will provide a machine-readable copy of the Source Code or +indicate a repository where this Source will be easily and freely available +for as long as the Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade +names, trademarks, service marks, or names of the Licensor, except as required +for reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she +brings to the Work are owned by him/her or licensed to him/her and that he/she +has the power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +Contributors. It is not a finished work and may therefore contain defects or +‘bugs’ inherent to this type of development. + +For the above reason, the Work is provided under the Licence on an ‘as is’ +basis and without warranties of any kind concerning the Work, including +without limitation merchantability, fitness for a particular purpose, absence +of defects or errors, accuracy, non-infringement of intellectual property +rights other than copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a +condition for the grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the +use of the Work, including without limitation, damages for loss of goodwill, +work stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such +damage. However, the Licensor will be liable under statutory product liability +laws as far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional +agreement, defining obligations or services consistent with this Licence. +However, if accepting obligations, You may act only on your own behalf and on +your sole responsibility, not on behalf of the original Licensor or any other +Contributor, and only if You agree to indemnify, defend, and hold each +Contributor harmless for any liability incurred by, or claims asserted against +such Contributor by the fact You have accepted any warranty or additional +liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon ‘I +agree’ placed under the bottom of a window displaying the text of this Licence +or by affirming consent in any other similar way, in accordance with the rules +of applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this +Licence, such as the use of the Work, the creation by You of a Derivative Work +or the Distribution or Communication by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of +electronic communication by You (for example, by offering to download the Work +from a remote location) the distribution channel or media (for example, a +website) must at least provide to the public the information requested by the +applicable law regarding the Licensor, the Licence and the way it may be +accessible, concluded, stored and reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed or reformed so as necessary to make it +valid and enforceable. + +The European Commission may publish other linguistic versions or new versions +of this Licence or updated versions of the Appendix, so far this is required +and reasonable, without reducing the scope of the rights granted by the +Licence. New versions of the Licence will be published with a unique version +number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, + +- any litigation resulting from the interpretation of this License, arising + between the European Union institutions, bodies, offices or agencies, as a + Licensor, and any Licensee, will be subject to the jurisdiction of the Court + of Justice of the European Union, as laid down in article 272 of the Treaty + on the Functioning of the European Union, + +- any litigation arising between other parties and resulting from the + interpretation of this License, will be subject to the exclusive + jurisdiction of the competent court where the Licensor resides or conducts + its primary business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, + +- this Licence shall be governed by the law of the European Union Member State + where the Licensor has his seat, resides or has his registered office, + +- this licence shall be governed by Belgian law if the Licensor has no seat, + residence or registered office inside a European Union Member State. + +Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: + +- GNU General Public License (GPL) v. 2, v. 3 +- GNU Affero General Public License (AGPL) v. 3 +- Open Software License (OSL) v. 2.1, v. 3.0 +- Eclipse Public License (EPL) v. 1.0 +- CeCILL v. 2.0, v. 2.1 +- Mozilla Public Licence (MPL) v. 2 +- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for + works other than software +- European Union Public Licence (EUPL) v. 1.1, v. 1.2 +- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong + Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the +above licences without producing a new version of the EUPL, as long as they +provide the rights granted in Article 2 of this Licence and protect the +covered Source Code from exclusive appropriation. + +All other changes or additions to this Appendix require the production of a +new EUPL version. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c10185 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Pokemon Game + +A work in progress Pokemon game intended for educational purposes. + +This project is an independent work and is not affiliated with Nintendo, Game Freak, or The Pokémon Company. \ No newline at end of file diff --git a/src/battle.rs b/src/battle.rs index f7b8fa7..76a954d 100644 --- a/src/battle.rs +++ b/src/battle.rs @@ -1,10 +1,76 @@ use crate::moves::{Move, MoveCategory}; use crate::pokemon::Pokemon; +use crate::trainer::Trainer; use crate::types::{Effectiveness, PokemonType}; use rand::Rng; +struct StatModifiers { + attack: i8, + defense: i8, + special_attack: i8, + special_defense: i8, + speed: i8, +} + +struct BattlePokemon { + pokemon: Pokemon, + stat_modifiers: StatModifiers, +} + +pub fn start_battle(player1: &mut Trainer, player2: &mut Trainer) { + todo!() +} + +pub fn perform_turn(pokemon1: &mut Pokemon, pokemon2: &mut Pokemon) { + let pokemon1_mv = choose_move(pokemon1); + let pokemon2_mv = choose_move(pokemon2); + + use_move(0, pokemon1, pokemon2); + if !pokemon2.is_fainted() { + use_move(0, pokemon2, pokemon1); + } else { + use_move(0, pokemon2, pokemon1); + if !pokemon1.is_fainted() { + use_move(0, pokemon1, pokemon2); + } + } +} + +pub fn battle(pokemon1: &mut Pokemon, pokemon2: &mut Pokemon) { + println!("{} and {} are fighting!", pokemon1.name, pokemon2.name); + + while !pokemon1.is_fainted() && !pokemon2.is_fainted() { + perform_turn(pokemon1, pokemon2); + } +} + +pub fn battle_with_random_moves(pokemon1: &mut Pokemon, pokemon2: &mut Pokemon) { + println!("{} and {} are fighting!", pokemon1.name, pokemon2.name); + let mut rng = rand::thread_rng(); + while !pokemon1.is_fainted() && !pokemon2.is_fainted() { + let pokemon1_move = rng.gen_range(0..pokemon1.moves.len()); + let pokemon2_move = rng.gen_range(0..pokemon2.moves.len()); + + if pokemon1.stats.speed >= pokemon2.stats.speed { + use_move(pokemon1_move, pokemon1, pokemon2); + if !pokemon2.is_fainted() { + use_move(pokemon2_move, pokemon2, pokemon1); + } + } else { + use_move(pokemon2_move, pokemon2, pokemon1); + if !pokemon1.is_fainted() { + use_move(pokemon1_move, pokemon1, pokemon2); + } + } + } +} + +fn choose_move(pokemon: &Pokemon) { + todo!() +} + pub fn use_move(move_index: usize, user: &mut Pokemon, target: &mut Pokemon) { - if !user.is_alive() { + if user.is_fainted() { println!("{} cannot move because it has fainted.", user.name); return; } @@ -12,7 +78,7 @@ pub fn use_move(move_index: usize, user: &mut Pokemon, target: &mut Pokemon) { let selected_move = &user.moves[move_index]; if selected_move.power > 0 { - if !target.is_alive() { + if target.is_fainted() { println!( "{} cannot attack {} because it has fainted.", user.name, target.name @@ -32,9 +98,9 @@ pub fn use_move(move_index: usize, user: &mut Pokemon, target: &mut Pokemon) { } } -fn get_move_damage(selected_move: &Move, user: &Pokemon, target: &Pokemon) -> i32 { +fn get_move_damage(selected_move: &Move, user: &Pokemon, target: &Pokemon) -> u32 { let effectiveness = - match PokemonType::get_effectiveness(selected_move.move_type, target.pokemon_type) { + match PokemonType::get_effectiveness(selected_move.move_type, PokemonType::Bug) { Effectiveness::NoEffect => { println!("It has no effect"); return 0; @@ -53,7 +119,7 @@ fn get_move_damage(selected_move: &Move, user: &Pokemon, target: &Pokemon) -> i3 } }; - let stab = if selected_move.move_type == user.pokemon_type { + let stab = if selected_move.move_type == PokemonType::Bug { 1.5 } else { 1.0 @@ -68,47 +134,5 @@ fn get_move_damage(selected_move: &Move, user: &Pokemon, target: &Pokemon) -> i3 }; let base_damage = level_modifier * selected_move.power as f32 * stat_ratio / 50.0 + 2.0; - (base_damage * stab * effectiveness).round() as i32 -} - -pub fn fight_once(pokemon1: &mut Pokemon, pokemon2: &mut Pokemon) { - if pokemon1.stats.speed >= pokemon2.stats.speed { - use_move(0, pokemon1, pokemon2); - if pokemon2.is_alive() { - use_move(0, pokemon2, pokemon1); - } - } else { - use_move(0, pokemon2, pokemon1); - if pokemon1.is_alive() { - use_move(0, pokemon1, pokemon2); - } - } -} - -pub fn fight(pokemon1: &mut Pokemon, pokemon2: &mut Pokemon) { - println!("{} and {} are fighting!", pokemon1.name, pokemon2.name); - while pokemon1.is_alive() && pokemon2.is_alive() { - fight_once(pokemon1, pokemon2); - } -} - -pub fn fight_with_random_moves(pokemon1: &mut Pokemon, pokemon2: &mut Pokemon) { - println!("{} and {} are fighting!", pokemon1.name, pokemon2.name); - let mut rng = rand::thread_rng(); - while pokemon1.is_alive() && pokemon2.is_alive() { - let pokemon1_move = rng.gen_range(0..pokemon1.moves.len()); - let pokemon2_move = rng.gen_range(0..pokemon2.moves.len()); - - if pokemon1.stats.speed >= pokemon2.stats.speed { - use_move(pokemon1_move, pokemon1, pokemon2); - if pokemon2.is_alive() { - use_move(pokemon2_move, pokemon2, pokemon1); - } - } else { - use_move(pokemon2_move, pokemon2, pokemon1); - if pokemon1.is_alive() { - use_move(pokemon1_move, pokemon1, pokemon2); - } - } - } + (base_damage * stab * effectiveness).round() as u32 } diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..d35b42e --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,3 @@ +pub const POKEMON_MAX_TYPES: usize = 2; +pub const POKEMON_MAX_MOVES: usize = 4; +pub const MAX_PARTY_SIZE: usize = 6; diff --git a/src/data/mod.rs b/src/data/mod.rs index af7f2a3..61686cb 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,2 +1,86 @@ -pub mod moves; -pub mod pokemon; +use crate::pokemon::{Pokemon, Stats}; +use crate::types::PokemonType; + +use crate::moves::{Move, MoveCategory}; + +fn load_pokemon_db() { + todo!(); +} + +pub fn get_pokemon(species: &str) -> Pokemon { + let types: Vec; + let stats: Stats; + let moves: Vec; + + match species { + "Bulbasaur" => { + types = vec![PokemonType::Grass]; + stats = Stats { + max_hp: 45, + attack: 49, + defense: 49, + special_attack: 65, + special_defense: 65, + speed: 45, + }; + moves = vec![get_move("Tackle"), get_move("Vine Whip")]; + } + + "Pikachu" => { + types = vec![PokemonType::Electric]; + stats = Stats { + max_hp: 35, + attack: 55, + defense: 30, + special_attack: 50, + special_defense: 40, + speed: 90, + }; + moves = vec![get_move("Tackle"), get_move("Thunderbolt")]; + } + + _ => unimplemented!("The Pokemon '{}' is not implemented.", species), + } + + Pokemon::builder(species) + .stats(stats) + .types(types) + .moves(moves) + .build() +} + +pub fn get_move(name: &str) -> Move { + match name { + "Tackle" => Move { + name: name.to_string(), + power: 40, + pp: 35, + max_pp: 35, + accuracy: 100, + move_type: PokemonType::Normal, + category: MoveCategory::Physical, + }, + + "Thunderbolt" => Move { + name: "Thunderbolt".to_string(), + power: 90, + pp: 15, + max_pp: 15, + accuracy: 100, + move_type: PokemonType::Electric, + category: MoveCategory::Special, + }, + + "Vine Whip" => Move { + name: name.to_string(), + power: 45, + pp: 25, + max_pp: 25, + accuracy: 100, + move_type: PokemonType::Grass, + category: MoveCategory::Physical, + }, + + _ => unimplemented!("The Move '{}' is not implemented.", name), + } +} diff --git a/src/data/moves.rs b/src/data/moves.rs deleted file mode 100644 index 2a5948e..0000000 --- a/src/data/moves.rs +++ /dev/null @@ -1,47 +0,0 @@ -use rand::Rng; - -use crate::moves::{Move, MoveCategory}; -use crate::pokemon::StatusCondition; -use crate::types::PokemonType; - -pub fn get_move(name: &str) -> Move { - match name { - "Tackle" => Move { - name: name.to_string(), - move_type: PokemonType::Normal, - category: MoveCategory::Physical, - power_point: 35, - power: 40, - accuracy: 100, - special_effect: None, - }, - - "Thunderbolt" => Move { - name: "Thunderbolt".to_string(), - move_type: PokemonType::Electric, - power_point: 15, - power: 90, - accuracy: 100, - category: MoveCategory::Special, - special_effect: Some(Box::new(|_user, _target| { - let mut rng = rand::thread_rng(); - if rng.gen_range(0..100) < 10 { - println!("{} is paralyzed!", _target.name); - _target.status = Some(StatusCondition::Paralyzed); - } - })), - }, - - "Vine Whip" => Move { - name: name.to_string(), - move_type: PokemonType::Grass, - category: MoveCategory::Physical, - power_point: 25, - power: 45, - accuracy: 100, - special_effect: None, - }, - - _ => unimplemented!("The Move '{}' is not implemented.", name), - } -} diff --git a/src/data/moves_db.json b/src/data/moves_db.json new file mode 100644 index 0000000..82ba2b9 --- /dev/null +++ b/src/data/moves_db.json @@ -0,0 +1,23 @@ +{ + "Tackle": { + "type": "Normal", + "category": "Physical", + "max_pp": 35, + "power": 40, + "accuracy": 100 + }, + "Thunderbolt": { + "move_type": "Electric", + "max_pp": 15, + "power": 90, + "accuracy": 100, + "category": "Special" + }, + "Vine Whip": { + "move_type": "Grass", + "category": "Physical", + "max_pp": 25, + "power": 45, + "accuracy": 100 + } +} \ No newline at end of file diff --git a/src/data/pokemon.rs b/src/data/pokemon.rs deleted file mode 100644 index 2c7a1b6..0000000 --- a/src/data/pokemon.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::data::moves::get_move; -use crate::moves::Move; -use crate::pokemon::{Pokemon, Stats}; -use crate::types::PokemonType; - -pub fn get_pokemon(name: &str) -> Pokemon { - let pokemon_type: PokemonType; - let stats: Stats; - let moves: Vec; - - match name { - "Bulbasaur" => { - pokemon_type = PokemonType::Grass; - stats = Stats { - hp: 45, - attack: 49, - defense: 49, - special_attack: 65, - special_defense: 65, - speed: 45, - }; - moves = vec![get_move("Tackle"), get_move("Vine Whip")]; - } - - "Pikachu" => { - pokemon_type = PokemonType::Electric; - stats = Stats { - hp: 35, - attack: 55, - defense: 30, - special_attack: 50, - special_defense: 40, - speed: 90, - }; - moves = vec![get_move("Tackle"), get_move("Thunderbolt")]; - } - - _ => unimplemented!("The Pokemon '{}' is not implemented.", name), - } - - let pokemon = Pokemon::new(name, pokemon_type, stats, moves); - - pokemon -} diff --git a/src/data/pokemon_db.json b/src/data/pokemon_db.json new file mode 100644 index 0000000..2f21601 --- /dev/null +++ b/src/data/pokemon_db.json @@ -0,0 +1,42 @@ +{ + "Bulbasaur": { + "id": 1, + "hp": 45, + "attack": 49, + "defense": 49, + "special_attack": 65, + "special_defense": 65, + "speed": 45, + "types": [ + "Grass" + ], + "moves": [ + "Vine Whip", + "Tackle" + ], + "evolves_to": { + "pokemon": "Ivysaur", + "level_required": 16 + } + }, + "Pikachu": { + "id": 25, + "hp": 35, + "attack": 55, + "defense": 30, + "special_attack": 50, + "special_defense": 40, + "speed": 90, + "types": [ + "Electric" + ], + "moves": [ + "Thunderbolt", + "Quick Attack" + ], + "evolves_to": { + "pokemon": "Raichu", + "item_required": "Thunder Stone" + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8241171..a6c6814 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,28 @@ -mod battle; +#![allow(dead_code, unused)] + +// mod battle; +mod constants; mod data; mod moves; mod pokemon; +mod trainer; mod types; +mod utils; -use battle::*; -use data::pokemon::get_pokemon; +// use battle::*; +use data::get_pokemon; +use pokemon::Pokemon; +use trainer::Trainer; fn main() { - let mut pikachu = get_pokemon("Pikachu"); - pikachu.level = 10; + let mut leaf = Trainer::new("Leaf"); + let mut red = Trainer::new("Red"); + + leaf.add_pokemon(get_pokemon("Bulbasaur")); + red.add_pokemon(get_pokemon("Pikachu")); - let mut bulbasaur = get_pokemon("Bulbasaur"); - bulbasaur.level = 10; - // let mut squirtle = get_pokemon("Squirtle"); + dbg!(leaf); + dbg!(red); - // fight(&mut pikachu, &mut bulbasaur); - fight_with_random_moves(&mut pikachu, &mut bulbasaur); - // dbg!(pikachu); - // dbg!(bulbasaur); + // start_battle(&mut leaf, &mut red); } diff --git a/src/moves.rs b/src/moves.rs index 744267a..eb4ec70 100644 --- a/src/moves.rs +++ b/src/moves.rs @@ -1,6 +1,4 @@ -use std::fmt::Debug; - -use crate::{pokemon::Pokemon, types::PokemonType}; +use crate::types::PokemonType; #[derive(Debug)] pub enum MoveCategory { @@ -8,26 +6,13 @@ pub enum MoveCategory { Special, } +#[derive(Debug)] pub struct Move { pub name: String, + pub power: u32, + pub pp: u8, + pub max_pp: u8, + pub accuracy: u8, pub move_type: PokemonType, pub category: MoveCategory, - pub power_point: u8, - pub power: u8, - pub accuracy: u8, - pub special_effect: Option>, -} - -impl Debug for Move { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Move") - .field("name", &self.name) - .field("move_type", &self.move_type) - .field("category", &self.category) - .field("power_point", &self.power_point) - .field("power", &self.power) - .field("accuracy", &self.accuracy) - // Omitting special_effect from Debug output - .finish() - } } diff --git a/src/pokemon.rs b/src/pokemon.rs index 9c472b5..60058a6 100644 --- a/src/pokemon.rs +++ b/src/pokemon.rs @@ -1,64 +1,147 @@ +use crate::constants::{POKEMON_MAX_MOVES, POKEMON_MAX_TYPES}; +use std::default; + use crate::moves::Move; use crate::types::PokemonType; -#[derive(Debug, Clone, Copy)] -pub struct Stats { - pub hp: i32, - pub attack: i32, - pub defense: i32, - pub special_attack: i32, - pub special_defense: i32, - pub speed: i32, -} +//////////////////////////////////////////////////////////////////////////////// +// Core Data Structures +//////////////////////////////////////////////////////////////////////////////// #[derive(Debug)] +#[non_exhaustive] pub enum StatusCondition { Paralyzed, } +#[derive(Debug)] +pub struct Stats { + pub max_hp: u32, + pub attack: u32, + pub defense: u32, + pub special_attack: u32, + pub special_defense: u32, + pub speed: u32, +} + +impl Default for Stats { + fn default() -> Self { + Self { + max_hp: 10, + attack: 10, + defense: 10, + special_attack: 10, + special_defense: 10, + speed: 10, + } + } +} + #[derive(Debug)] pub struct Pokemon { pub name: String, - pub pokemon_type: PokemonType, - pub stats: Stats, - pub level: u8, - pub current_hp: i32, - pub moves: Vec, - pub status: Option, + species: String, + level: u8, + hp: u32, + stats: Stats, + types: Vec, + moves: Vec, + status: Option, } impl Pokemon { - pub fn new(name: &str, pokemon_type: PokemonType, stats: Stats, moves: Vec) -> Self { - let pokemon = Self { - name: name.to_string(), - pokemon_type, - stats, + pub fn new(species: &str) -> Pokemon { + Self::builder(species).build() + } + + pub fn builder(species: &str) -> PokemonBuilder { + PokemonBuilder { + species: species.into(), level: 1, - current_hp: stats.hp, - moves, - status: None, - }; + stats: Stats::default(), + types: vec![PokemonType::default()], + moves: Vec::new(), + } + } - println!("{} spawned with {} health", name, pokemon.current_hp); + pub fn from_species(name: &str) -> Self { + todo!() + } - pokemon + pub fn level_up(&mut self) { + todo!() } - pub fn is_alive(&self) -> bool { - self.current_hp > 0 + pub fn is_fainted(&self) -> bool { + self.hp == 0 } - pub fn take_damage(&mut self, amount: i32) { - if !self.is_alive() { - return; - } - println!("{} took {} damage!", self.name, amount); - self.current_hp = (self.current_hp - amount).max(0); + pub fn heal(&mut self, amount: u32) { + self.hp = (self.hp + amount).min(self.stats.max_hp); + } - if self.is_alive() { - println!("{}'s remaining health: {}", self.name, self.current_hp); - } else { - println!("{} fainted.", self.name); + pub fn full_heal(&mut self) { + self.hp = self.stats.max_hp; + } + + pub fn take_damage(&mut self, amount: u32) { + self.hp = self.hp.saturating_sub(amount); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Creation Logic +//////////////////////////////////////////////////////////////////////////////// + +#[derive(Debug)] +pub struct PokemonBuilder { + species: String, + level: u8, + stats: Stats, + types: Vec, + moves: Vec, +} + +impl PokemonBuilder { + pub fn level(mut self, level: u8) -> Self { + todo!() + } + + pub fn stats(mut self, stats: Stats) -> Self { + self.stats = stats; + self + } + + pub fn types(mut self, types: Vec) -> Self { + assert!( + types.len() <= POKEMON_MAX_TYPES, + "Cannot create a Pokemon with more than {} types.", + POKEMON_MAX_TYPES + ); + self.types = types; + self + } + + pub fn moves(mut self, moves: Vec) -> Self { + assert!( + moves.len() <= POKEMON_MAX_MOVES, + "Cannot create a Pokemon with more than {} moves.", + POKEMON_MAX_MOVES + ); + self.moves = moves; + self + } + + pub fn build(self) -> Pokemon { + Pokemon { + name: self.species.clone(), + species: self.species, + level: self.level, + hp: self.stats.max_hp, + stats: self.stats, + types: self.types, + moves: self.moves, + status: None, } } } diff --git a/src/trainer.rs b/src/trainer.rs new file mode 100644 index 0000000..ae48699 --- /dev/null +++ b/src/trainer.rs @@ -0,0 +1,75 @@ +use crate::constants::MAX_PARTY_SIZE; +use crate::pokemon::Pokemon; + +#[derive(Debug)] +pub struct Party { + members: Vec, +} + +impl Party { + pub fn new() -> Self { + Self { + members: Vec::new(), + } + } + + pub fn from_pokemon(pokemon: Vec) -> Self { + assert!( + pokemon.len() <= MAX_PARTY_SIZE, + "Cannot create a party with more than {} Pokemon.", + MAX_PARTY_SIZE + ); + Self { members: pokemon } + } + + pub fn size(&self) -> usize { + self.members.len() + } + + pub fn is_full(&self) -> bool { + self.size() >= MAX_PARTY_SIZE + } + + pub fn add(&mut self, pokemon: Pokemon) -> Result<(), String> { + if self.is_full() { + Err(format!("Team is full! Cannot add {}.", pokemon.name)) + } else { + self.members.push(pokemon); + Ok(()) + } + } +} + +#[derive(Debug)] +pub struct Trainer { + pub name: String, + pub party: Party, +} + +impl Trainer { + pub fn new(name: &str) -> Self { + Self { + name: name.to_string(), + party: Party::new(), + } + } + + pub fn with_pokemon(trainer_name: &str, pokemon: Vec) -> Self { + Self { + name: trainer_name.to_string(), + party: Party::from_pokemon(pokemon), + } + } + + pub fn is_party_full(&self) -> bool { + self.party.is_full() + } + + pub fn add_pokemon(&mut self, pokemon: Pokemon) -> Result<(), String> { + self.party.add(pokemon) + } + + pub fn get_party(&self) -> &[Pokemon] { + &self.party.members + } +} diff --git a/src/types.rs b/src/types.rs index 331ce97..86cc994 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,25 @@ +// Source: [Generations II-V](https://bulbapedia.bulbagarden.net/wiki/Type/Type_chart) +const TYPE_TABLE: [[u8; 17]; 17] = [ + // Fi Fl Po Gd Ro Bg Gh St Fi Wa Gs El Ps Ic Dr Da + [2, 2, 2, 2, 2, 1, 2, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2], // Normal + [4, 2, 1, 1, 2, 4, 1, 0, 4, 2, 2, 2, 2, 1, 4, 2, 4], // Fighting + [2, 4, 2, 2, 2, 1, 4, 2, 1, 2, 2, 4, 1, 2, 2, 2, 2], // Flying + [2, 2, 2, 1, 1, 1, 2, 1, 0, 2, 2, 4, 2, 2, 2, 2, 2], // Poison + [2, 2, 0, 4, 2, 4, 1, 2, 4, 4, 2, 1, 4, 2, 2, 2, 2], // Ground + [2, 1, 4, 2, 1, 2, 4, 2, 1, 4, 2, 2, 2, 2, 4, 2, 2], // Rock + [2, 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 4, 2, 4, 2, 2, 4], // Bug + [0, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 4, 2, 2, 1], // Ghost + [2, 2, 2, 2, 2, 4, 2, 2, 1, 1, 1, 2, 1, 2, 4, 2, 2], // Steel + [2, 2, 2, 2, 2, 1, 4, 2, 4, 1, 1, 4, 2, 2, 4, 1, 2], // Fire + [2, 2, 2, 2, 4, 4, 2, 2, 2, 4, 1, 1, 2, 2, 2, 1, 2], // Water + [2, 2, 1, 1, 4, 4, 1, 2, 1, 1, 4, 1, 2, 2, 2, 1, 2], // Grass + [2, 2, 4, 2, 0, 2, 2, 2, 2, 2, 4, 1, 1, 2, 2, 1, 2], // Electric + [2, 4, 2, 4, 2, 2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 0], // Psychic + [2, 2, 4, 2, 4, 2, 2, 2, 1, 1, 1, 4, 2, 2, 1, 4, 2], // Ice + [2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 4, 2], // Dragon + [2, 1, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 4, 2, 2, 1], // Dark +]; + #[derive(Debug, Clone, Copy, PartialEq)] pub enum Effectiveness { NoEffect, @@ -7,8 +29,9 @@ pub enum Effectiveness { } #[allow(dead_code)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] pub enum PokemonType { + #[default] Normal, Fighting, Flying, @@ -28,65 +51,81 @@ pub enum PokemonType { Dark, } -// Source: [Generations II-V](https://bulbapedia.bulbagarden.net/wiki/Type/Type_chart) -const TYPE_TABLE: [[u8; 17]; 17] = [ - [2, 2, 2, 2, 2, 1, 2, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2], // Normal - [4, 2, 1, 1, 2, 4, 1, 0, 4, 2, 2, 2, 2, 1, 4, 2, 4], // Fighting - [2, 4, 2, 2, 2, 1, 4, 2, 1, 2, 2, 4, 1, 2, 2, 2, 2], // Flying - [2, 2, 2, 1, 1, 1, 2, 1, 0, 2, 2, 4, 2, 2, 2, 2, 2], // Poison - [2, 2, 0, 4, 2, 4, 1, 2, 4, 4, 2, 1, 4, 2, 2, 2, 2], // Ground - [2, 1, 4, 2, 1, 2, 4, 2, 1, 4, 2, 2, 2, 2, 4, 2, 2], // Rock - [2, 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 4, 2, 4, 2, 2, 4], // Bug - [0, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 4, 2, 2, 1], // Ghost - [2, 2, 2, 2, 2, 4, 2, 2, 1, 1, 1, 2, 1, 2, 4, 2, 2], // Steel - [2, 2, 2, 2, 2, 1, 4, 2, 4, 1, 1, 4, 2, 2, 4, 1, 2], // Fire - [2, 2, 2, 2, 4, 4, 2, 2, 2, 4, 1, 1, 2, 2, 2, 1, 2], // Water - [2, 2, 1, 1, 4, 4, 1, 2, 1, 1, 4, 1, 2, 2, 2, 1, 2], // Grass - [2, 2, 4, 2, 0, 2, 2, 2, 2, 2, 4, 1, 1, 2, 2, 1, 2], // Electric - [2, 4, 2, 4, 2, 2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 0], // Psychic - [2, 2, 4, 2, 4, 2, 2, 2, 1, 1, 1, 4, 2, 2, 1, 4, 2], // Ice - [2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 4, 2], // Dragon - [2, 1, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 4, 2, 2, 1], // Dark -]; - impl PokemonType { - pub fn get_effectiveness(attacker: PokemonType, defender: PokemonType) -> Effectiveness { - match TYPE_TABLE[attacker as usize][defender as usize] { - 0 => Effectiveness::NoEffect, - 1 => Effectiveness::NotVeryEffective, - 2 => Effectiveness::Neutral, - 4 => Effectiveness::SuperEffective, - _ => unreachable!(), - } - } - - fn _get_effectiveness_by_match(attacker: PokemonType, defender: PokemonType) -> Effectiveness { + fn _get_effectiveness(attacker: PokemonType, defender: PokemonType) -> Effectiveness { + use Effectiveness::*; + use PokemonType::*; match (attacker, defender) { - (PokemonType::Normal, PokemonType::Rock) => Effectiveness::NotVeryEffective, - (PokemonType::Normal, PokemonType::Ghost) => Effectiveness::NoEffect, + (Normal, Rock) => NotVeryEffective, + (Normal, Rock) => NotVeryEffective, + (Normal, Ghost) => NoEffect, + (Normal, Steel) => NotVeryEffective, + + (Fighting, Normal) => SuperEffective, + (Fighting, Flying) => NotVeryEffective, + (Fighting, Poison) => NotVeryEffective, + (Fighting, Rock) => SuperEffective, + (Fighting, Ghost) => NoEffect, + (Fighting, Steel) => SuperEffective, + (Fighting, Psychic) => NotVeryEffective, + (Fighting, Ice) => SuperEffective, + (Fighting, Dark) => SuperEffective, + + (Flying, Fighting) => SuperEffective, + (Flying, Rock) => NotVeryEffective, + (Flying, Bug) => SuperEffective, + (Flying, Steel) => NotVeryEffective, + (Flying, Grass) => SuperEffective, + (Flying, Electric) => NotVeryEffective, + + (Poison, Poison) => NotVeryEffective, + (Poison, Ground) => NotVeryEffective, + (Poison, Rock) => NotVeryEffective, + (Poison, Ghost) => NotVeryEffective, + (Poison, Steel) => NoEffect, + (Poison, Grass) => SuperEffective, - (PokemonType::Fire, PokemonType::Grass) => Effectiveness::SuperEffective, - (PokemonType::Water, PokemonType::Grass) => Effectiveness::NotVeryEffective, - (PokemonType::Fighting, PokemonType::Ghost) => Effectiveness::NoEffect, + (Ground, Flying) => NoEffect, + (Ground, Poison) => SuperEffective, + (Ground, Rock) => SuperEffective, + (Ground, Bug) => NotVeryEffective, + (Ground, Steel) => SuperEffective, + (Ground, Fire) => SuperEffective, + (Ground, Grass) => NotVeryEffective, + (Ground, Electric) => SuperEffective, - (PokemonType::Grass, PokemonType::Flying) => Effectiveness::NotVeryEffective, - (PokemonType::Grass, PokemonType::Poison) => Effectiveness::NotVeryEffective, - (PokemonType::Grass, PokemonType::Ground) => Effectiveness::SuperEffective, - (PokemonType::Grass, PokemonType::Rock) => Effectiveness::SuperEffective, - (PokemonType::Grass, PokemonType::Bug) => Effectiveness::NotVeryEffective, - (PokemonType::Grass, PokemonType::Fire) => Effectiveness::NotVeryEffective, - (PokemonType::Grass, PokemonType::Water) => Effectiveness::SuperEffective, - (PokemonType::Grass, PokemonType::Grass) => Effectiveness::NotVeryEffective, - (PokemonType::Grass, PokemonType::Dragon) => Effectiveness::NotVeryEffective, + (Fire, Grass) => SuperEffective, + (Water, Grass) => NotVeryEffective, - (PokemonType::Electric, PokemonType::Flying) => Effectiveness::SuperEffective, - (PokemonType::Electric, PokemonType::Ground) => Effectiveness::NoEffect, - (PokemonType::Electric, PokemonType::Water) => Effectiveness::SuperEffective, - (PokemonType::Electric, PokemonType::Grass) => Effectiveness::NotVeryEffective, - (PokemonType::Electric, PokemonType::Electric) => Effectiveness::NotVeryEffective, - (PokemonType::Electric, PokemonType::Dragon) => Effectiveness::NotVeryEffective, + (Grass, Flying) => NotVeryEffective, + (Grass, Poison) => NotVeryEffective, + (Grass, Ground) => SuperEffective, + (Grass, Rock) => SuperEffective, + (Grass, Bug) => NotVeryEffective, + (Grass, Fire) => NotVeryEffective, + (Grass, Water) => SuperEffective, + (Grass, Grass) => NotVeryEffective, + (Grass, Dragon) => NotVeryEffective, - _ => Effectiveness::Neutral, + (Electric, Flying) => SuperEffective, + (Electric, Ground) => NoEffect, + (Electric, Water) => SuperEffective, + (Electric, Grass) => NotVeryEffective, + (Electric, Electric) => NotVeryEffective, + (Electric, Dragon) => NotVeryEffective, + + _ => Neutral, + } + } + + pub fn get_effectiveness(attacker: PokemonType, defender: PokemonType) -> Effectiveness { + use Effectiveness::*; + match TYPE_TABLE[attacker as usize][defender as usize] { + 0 => NoEffect, + 1 => NotVeryEffective, + 2 => Neutral, + 4 => SuperEffective, + _ => unreachable!(), } } } @@ -118,19 +157,19 @@ mod tests { #[test] fn test_get_effectiveness_by_match() { assert_eq!( - PokemonType::_get_effectiveness_by_match(PokemonType::Fire, PokemonType::Grass), + PokemonType::_get_effectiveness(PokemonType::Fire, PokemonType::Grass), Effectiveness::SuperEffective ); assert_eq!( - PokemonType::_get_effectiveness_by_match(PokemonType::Water, PokemonType::Grass), + PokemonType::_get_effectiveness(PokemonType::Water, PokemonType::Grass), Effectiveness::NotVeryEffective ); assert_eq!( - PokemonType::_get_effectiveness_by_match(PokemonType::Fighting, PokemonType::Ghost), + PokemonType::_get_effectiveness(PokemonType::Fighting, PokemonType::Ghost), Effectiveness::NoEffect ); assert_eq!( - PokemonType::_get_effectiveness_by_match(PokemonType::Normal, PokemonType::Normal), + PokemonType::_get_effectiveness(PokemonType::Normal, PokemonType::Normal), Effectiveness::Neutral ); } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..09ed505 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,7 @@ +use crate::Pokemon; + +pub fn print_party_members(members: &[Pokemon]) { + let names: Vec = members.iter().map(|pokemon| pokemon.name.clone()).collect(); + let s = format!("[ {} ]", names.join(", ")); + println!("{}", s); +}