Compare commits
17 Commits
f7f46ba96e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02b74c8ee5 | ||
|
|
42831b71a1 | ||
|
|
8e3cecdc6a | ||
|
|
e94a802034 | ||
|
|
9130797eec | ||
|
|
61f7299e42 | ||
|
|
5bf7d3b1d8 | ||
|
|
dfc99e97e3 | ||
|
|
dcbdc29f78 | ||
|
|
fa3c80105f | ||
|
|
f493a5e98c | ||
|
|
6007c2f2ed | ||
|
|
50841b807a | ||
|
|
9edb4a224c | ||
|
|
262c962d5d | ||
|
|
5b60e34801 | ||
|
|
e6fa3a9293 |
27
src/rust/Cargo.lock → src/gmon/Cargo.lock
generated
Normal file → Executable file
27
src/rust/Cargo.lock → src/gmon/Cargo.lock
generated
Normal file → Executable file
@@ -237,6 +237,18 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "gmon"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"godot",
|
||||
"mon",
|
||||
"native_db",
|
||||
"native_model",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "godot"
|
||||
version = "0.2.4"
|
||||
@@ -344,6 +356,10 @@ version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "mon"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "nanoserde"
|
||||
version = "0.1.37"
|
||||
@@ -436,17 +452,6 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "price-is-wrong"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"godot",
|
||||
"native_db",
|
||||
"native_model",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
3
src/rust/Cargo.toml → src/gmon/Cargo.toml
Normal file → Executable file
3
src/rust/Cargo.toml → src/gmon/Cargo.toml
Normal file → Executable file
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "price-is-wrong"
|
||||
name = "gmon"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
@@ -9,6 +9,7 @@ godot = "0.2.4"
|
||||
native_db = "0.8.1"
|
||||
native_model = "0.4.20"
|
||||
serde = "1.0.218"
|
||||
mon = { path = "../mon" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
26
src/gmon/src/battle.rs
Normal file
26
src/gmon/src/battle.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use ::mon::*;
|
||||
use godot::classes::{ISprite2D, Sprite2D};
|
||||
use godot::prelude::*;
|
||||
|
||||
pub type Instance = mon::battle::Instance;
|
||||
pub type Team = mon::battle::Team;
|
||||
pub type Event = mon::battle::Event;
|
||||
pub type Move = mon::battle::Move;
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Sprite2D)]
|
||||
struct Engine {
|
||||
instance: mon::battle::Engine,
|
||||
|
||||
base: Base<Sprite2D>,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl ISprite2D for Engine {
|
||||
fn init(base: Base<Sprite2D>) -> Self {
|
||||
Self {
|
||||
instance: mon::battle::Engine::new(),
|
||||
base: base,
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/gmon/src/lib.rs
Executable file
9
src/gmon/src/lib.rs
Executable file
@@ -0,0 +1,9 @@
|
||||
use godot::prelude::*;
|
||||
|
||||
struct MyExtension;
|
||||
|
||||
#[gdextension]
|
||||
unsafe impl ExtensionLibrary for MyExtension {}
|
||||
|
||||
pub mod battle;
|
||||
pub mod mon;
|
||||
5
src/gmon/src/mon.rs
Normal file
5
src/gmon/src/mon.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use ::mon::*;
|
||||
|
||||
pub type Instance = mon::mon::Instance;
|
||||
pub type Species = mon::mon::Species;
|
||||
pub type Dex = mon::mon::Dex;
|
||||
@@ -1,4 +1,4 @@
|
||||
# Price is Wrong
|
||||
# Mon
|
||||
|
||||
Like https://gitea.inhome.blapointe.com/bel/price-is-wrong but
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ compatibility_minimum = 4.1
|
||||
reloadable = true
|
||||
|
||||
[libraries]
|
||||
linux.debug.x86_64 = "res://../rust/target/debug/libprice_is_wrong.so"
|
||||
linux.release.x86_64 = "res://../rust/target/release/libprice_is_wrong.so"
|
||||
windows.debug.x86_64 = "res://../rust/target/debug/price_is_wrong.dll"
|
||||
windows.release.x86_64 = "res://../rust/target/release/price_is_wrong.dll"
|
||||
macos.debug = "res://../rust/target/debug/libprice_is_wrong.dylib"
|
||||
macos.release = "res://../rust/target/release/libprice_is_wrong.dylib"
|
||||
macos.debug.arm64 = "res://../rust/target/debug/libprice_is_wrong.dylib"
|
||||
macos.release.arm64 = "res://../rust/target/release/libprice_is_wrong.dylib"
|
||||
linux.debug.x86_64 = "res://../gmon/target/debug/libgmon.so"
|
||||
linux.release.x86_64 = "res://../gmon/target/release/libgmon.so"
|
||||
windows.debug.x86_64 = "res://../gmon/target/debug/gmon.dll"
|
||||
windows.release.x86_64 = "res://../gmon/target/release/gmon.dll"
|
||||
macos.debug = "res://../gmon/target/debug/libgmon.dylib"
|
||||
macos.release = "res://../gmon/target/release/libgmon.dylib"
|
||||
macos.debug.arm64 = "res://../gmon/target/debug/libgmon.dylib"
|
||||
macos.release.arm64 = "res://../gmon/target/release/libgmon.dylib"
|
||||
|
||||
@@ -10,7 +10,7 @@ config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="price-is-wrong"
|
||||
config/name="mon"
|
||||
run/main_scene="res://gd/node_2d.tscn"
|
||||
config/features=PackedStringArray("4.3", "GL Compatibility")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
7
src/mon/Cargo.lock
generated
Normal file
7
src/mon/Cargo.lock
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "mon"
|
||||
version = "0.1.0"
|
||||
8
src/mon/Cargo.toml
Executable file
8
src/mon/Cargo.toml
Executable file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "mon"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[lib]
|
||||
1
src/mon/src/lib.rs
Executable file
1
src/mon/src/lib.rs
Executable file
@@ -0,0 +1 @@
|
||||
pub mod mon;
|
||||
574
src/mon/src/mon.rs
Executable file
574
src/mon/src/mon.rs
Executable file
@@ -0,0 +1,574 @@
|
||||
pub mod mon {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Species {
|
||||
pub name: String,
|
||||
pub hp: i32,
|
||||
pub atk: i32,
|
||||
pub def: i32,
|
||||
pub spd: i32,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod species_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_literal() {
|
||||
let _: Species = Species {
|
||||
name: "".to_string(),
|
||||
hp: 1,
|
||||
atk: 2,
|
||||
def: 3,
|
||||
spd: 4,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Dex {
|
||||
Pika,
|
||||
Mari,
|
||||
}
|
||||
|
||||
impl Dex {
|
||||
pub fn new(&self) -> Species {
|
||||
self.clone().into()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.new().name
|
||||
}
|
||||
|
||||
pub fn hp(&self) -> i32 {
|
||||
self.new().hp
|
||||
}
|
||||
|
||||
pub fn atk(&self) -> i32 {
|
||||
self.new().atk
|
||||
}
|
||||
|
||||
pub fn def(&self) -> i32 {
|
||||
self.new().def
|
||||
}
|
||||
|
||||
pub fn spd(&self) -> i32 {
|
||||
self.new().spd
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Species> for Dex {
|
||||
fn into(self) -> Species {
|
||||
match self {
|
||||
Dex::Pika => Species {
|
||||
name: "Pika".to_string(),
|
||||
hp: 10,
|
||||
atk: 11,
|
||||
def: 9,
|
||||
spd: 11,
|
||||
},
|
||||
Dex::Mari => Species {
|
||||
name: "Mari".to_string(),
|
||||
hp: 10,
|
||||
atk: 8,
|
||||
def: 12,
|
||||
spd: 9,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod dex_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dex_to_species() {
|
||||
let dex = Dex::Pika;
|
||||
let _: Species = dex.new();
|
||||
let _: Species = dex.into();
|
||||
let _: Species = Dex::Pika.new();
|
||||
let _: Species = Dex::Pika.into();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Instance {
|
||||
pub dex: Dex,
|
||||
pub damage: i32,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub fn roll(dex: Dex) -> Instance {
|
||||
Instance {
|
||||
dex: dex,
|
||||
damage: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hit(&mut self, damage: i32) {
|
||||
let cap = self.dex.new().hp;
|
||||
self.damage = match self.damage + damage {
|
||||
v if v < cap => v,
|
||||
_ => cap,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod instance_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_instance_roll() {
|
||||
let i: Instance = Instance::roll(Dex::Pika);
|
||||
assert_eq!(0, i.damage);
|
||||
assert_eq!(Dex::Pika, i.dex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod battle {
|
||||
use super::*;
|
||||
|
||||
pub struct Engine {
|
||||
teams: Vec<Team>,
|
||||
q: Vec<Move>,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn new(first: Vec<mon::Instance>, second: Vec<mon::Instance>) -> Engine {
|
||||
let mut result = Self {
|
||||
teams: vec![],
|
||||
q: vec![],
|
||||
};
|
||||
result.join(first);
|
||||
result.join(second);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn join(&mut self, third: Vec<mon::Instance>) {
|
||||
self.teams.push(Team::new(third));
|
||||
}
|
||||
|
||||
pub fn enqueue(&mut self, m: Move) -> Result<(), String> {
|
||||
match m {
|
||||
Move::Attack(w_idx, r_idx) => {
|
||||
if self.teams()[w_idx].lost {
|
||||
return Err(format!("team[{w_idx}] lost"));
|
||||
}
|
||||
if !self.teams()[w_idx].any_can_attack() {
|
||||
return Err(format!("team[{w_idx}] cannot attack"));
|
||||
}
|
||||
if !self.teams()[r_idx].any_can_be_attacked() {
|
||||
return Err(format!("team[{r_idx}] cannot be attacked"));
|
||||
}
|
||||
}
|
||||
Move::Pass(team_idx) => {
|
||||
if self.teams()[team_idx].lost {
|
||||
return Err(format!("team[{team_idx}] lost"));
|
||||
}
|
||||
if !self.teams()[team_idx].any_can_attack() {
|
||||
return Err(format!("team[{team_idx}] cannot move"));
|
||||
}
|
||||
}
|
||||
Move::Swap(team_idx, mon_idx) => {
|
||||
if self.teams()[team_idx].lost {
|
||||
return Err(format!("team[{team_idx}] lost"));
|
||||
}
|
||||
if !self.teams()[team_idx].mons()[mon_idx].alive() {
|
||||
return Err(format!("team[{team_idx}].mons[{mon_idx}] cannot swap in"));
|
||||
}
|
||||
if self.teams()[team_idx].mons()[mon_idx].out {
|
||||
return Err(format!("team[{team_idx}].mons[{mon_idx}] already in"));
|
||||
}
|
||||
}
|
||||
};
|
||||
self.q.push(m);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exec(&mut self) -> Vec<Event> {
|
||||
self.turn_order().iter().for_each(|idx| {
|
||||
match self.q[*idx].clone() {
|
||||
Move::Pass(_) => {}
|
||||
Move::Attack(r_team_idx, w_team_idx) => {
|
||||
let r_team = self.teams[r_team_idx].clone();
|
||||
let w_team = &mut self.teams[w_team_idx];
|
||||
|
||||
for r_mon_idx in 0..r_team.mons().len() {
|
||||
let r_mon = &r_team.mons()[r_mon_idx];
|
||||
if !r_mon.can_attack() {
|
||||
continue;
|
||||
}
|
||||
for w_mon_idx in 0..w_team.mons().len() {
|
||||
let w_mon = &w_team.mons()[w_mon_idx];
|
||||
if !w_mon.can_be_attacked() {
|
||||
continue;
|
||||
}
|
||||
let r_mon = &r_mon.mon;
|
||||
let w_mon = &w_mon.mon;
|
||||
w_team.mons[w_mon_idx].mon = Engine::attack(w_mon, r_mon);
|
||||
w_team.mons[w_mon_idx].out =
|
||||
w_team.mons[w_mon_idx].out && w_team.mons[w_mon_idx].alive()
|
||||
}
|
||||
}
|
||||
|
||||
self.teams[w_team_idx] = w_team.to_owned();
|
||||
}
|
||||
Move::Swap(team_idx, mon_idx) => {
|
||||
let mut team = self.teams[team_idx].clone();
|
||||
team.mons = team
|
||||
.mons()
|
||||
.iter()
|
||||
.map(|m| {
|
||||
let mut m = m.clone();
|
||||
m.out = false;
|
||||
m
|
||||
})
|
||||
.collect();
|
||||
team.mons[mon_idx].out = true;
|
||||
self.teams[team_idx] = team;
|
||||
}
|
||||
};
|
||||
});
|
||||
self.new_turn()
|
||||
}
|
||||
|
||||
pub fn teams(&self) -> Vec<Team> {
|
||||
self.teams.iter().map(|t| t.clone()).collect()
|
||||
}
|
||||
|
||||
fn new_turn(&mut self) -> Vec<Event> {
|
||||
self.q = vec![];
|
||||
|
||||
let mut result = vec![];
|
||||
|
||||
let teams = self.teams();
|
||||
let mut teams_not_lost = vec![];
|
||||
for i in 0..teams.len() {
|
||||
let has_mon_out = teams[i].mons().iter().filter(|m| m.out).count() > 0;
|
||||
let has_mon_alive = teams[i].mons().iter().filter(|m| m.alive()).count() > 0;
|
||||
let lost_before = teams[i].lost;
|
||||
|
||||
if !has_mon_out && has_mon_alive {
|
||||
result.push(Event::MustSwap(i));
|
||||
} else if !has_mon_alive && !lost_before {
|
||||
self.teams[i].lost = true;
|
||||
result.push(Event::Lose(i));
|
||||
}
|
||||
|
||||
if !self.teams()[i].lost {
|
||||
teams_not_lost.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
if teams_not_lost.len() == 1 {
|
||||
result.push(Event::Win(teams_not_lost[0]));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn turn_order(&self) -> Vec<usize> {
|
||||
let mut order = vec![];
|
||||
for i in 0..self.q.len() {
|
||||
order.push((
|
||||
i,
|
||||
match &self.q[i] {
|
||||
Move::Pass(_) => 0,
|
||||
Move::Swap(_, _) => -10,
|
||||
Move::Attack(r_team, _) => self.teams[r_team.to_owned()]
|
||||
.mons
|
||||
.iter()
|
||||
.filter(|m| m.can_attack())
|
||||
.map(|m| m.mon.dex.new().spd)
|
||||
.collect::<Vec<_>>()
|
||||
.first()
|
||||
.expect("team had no pokemon to attack")
|
||||
.to_owned(),
|
||||
},
|
||||
));
|
||||
}
|
||||
order.sort_unstable_by(|a, b| a.1.cmp(&b.1));
|
||||
order.iter().map(|x| x.0).collect()
|
||||
}
|
||||
|
||||
fn attack(w: &mon::Instance, r: &mon::Instance) -> mon::Instance {
|
||||
let mut w = w.clone();
|
||||
let atk_delta = r.dex.new().atk - 10;
|
||||
let def_delta = w.dex.new().def - 10;
|
||||
|
||||
let power = match atk_delta - def_delta {
|
||||
v if v < 1 => 1,
|
||||
v => v,
|
||||
};
|
||||
|
||||
w.hit(power);
|
||||
|
||||
w
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod engine_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
let mut engine = Engine::new(team_a(), team_b());
|
||||
engine.join(team_b());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attack() {
|
||||
let mut pika = mon::Instance::roll(mon::Dex::Pika);
|
||||
let mut mari = mon::Instance::roll(mon::Dex::Mari);
|
||||
|
||||
pika = Engine::attack(&mut pika, &mut mari);
|
||||
assert_eq!(1, pika.damage);
|
||||
|
||||
mari = Engine::attack(&mut mari, &mut pika);
|
||||
assert_eq!(1, mari.damage);
|
||||
|
||||
for _ in 0..pika.dex.new().hp + 5 {
|
||||
pika = Engine::attack(&mut pika, &mut mari);
|
||||
}
|
||||
assert_eq!(pika.dex.new().hp, pika.damage);
|
||||
assert_eq!(1, mari.damage);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_turn() {
|
||||
let mut engine = Engine::new(team_a(), team_b());
|
||||
engine.join(team_b());
|
||||
|
||||
// player 0 mon 0 attacks team 1 mon 0
|
||||
engine
|
||||
.enqueue(Move::Attack(0, 1))
|
||||
.expect("failed to attack");
|
||||
// player 1 mon 0 passes
|
||||
engine.enqueue(Move::Pass(1)).expect("failed to pass");
|
||||
// player 1 mon 1 not out
|
||||
assert_eq!(0, engine.exec().len());
|
||||
|
||||
assert_eq!(0, engine.teams()[0].mons()[0].mon.damage);
|
||||
assert_eq!(1, engine.teams()[1].mons()[0].mon.damage);
|
||||
assert_eq!(0, engine.teams()[1].mons()[1].mon.damage);
|
||||
}
|
||||
|
||||
fn team_a() -> Vec<mon::Instance> {
|
||||
vec![mon::Instance::roll(mon::Dex::Pika)]
|
||||
}
|
||||
|
||||
fn team_b() -> Vec<mon::Instance> {
|
||||
vec![
|
||||
mon::Instance::roll(mon::Dex::Mari),
|
||||
mon::Instance::roll(mon::Dex::Pika),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Team {
|
||||
mons: Vec<Instance>,
|
||||
lost: bool,
|
||||
}
|
||||
|
||||
impl Team {
|
||||
fn new(mons: Vec<mon::Instance>) -> Self {
|
||||
assert!(mons.len() > 0);
|
||||
let mut mons: Vec<_> = mons
|
||||
.iter()
|
||||
.map(|m| Instance {
|
||||
mon: m.clone(),
|
||||
out: false,
|
||||
})
|
||||
.collect();
|
||||
mons[0].out = true;
|
||||
Self {
|
||||
mons: mons.clone(),
|
||||
lost: mons.len() == 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mons(&self) -> Vec<Instance> {
|
||||
self.mons.iter().map(|i| i.clone()).collect()
|
||||
}
|
||||
|
||||
fn any_can_attack(&self) -> bool {
|
||||
self.mons().iter().filter(|m| m.can_attack()).count() > 0
|
||||
}
|
||||
|
||||
fn any_can_be_attacked(&self) -> bool {
|
||||
self.mons().iter().filter(|m| m.can_be_attacked()).count() > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Instance {
|
||||
pub mon: mon::Instance,
|
||||
pub out: bool,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
fn alive(&self) -> bool {
|
||||
self.damage() < self.mon.dex.new().hp
|
||||
}
|
||||
|
||||
fn can_attack(&self) -> bool {
|
||||
self.out && self.alive()
|
||||
}
|
||||
|
||||
fn can_be_attacked(&self) -> bool {
|
||||
self.out && self.alive()
|
||||
}
|
||||
|
||||
pub fn damage(&self) -> i32 {
|
||||
self.mon.damage
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod instance_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_checks() {
|
||||
let mut i = Instance {
|
||||
mon: mon::Instance::roll(mon::Dex::Pika),
|
||||
out: true,
|
||||
};
|
||||
assert_eq!(true, i.alive());
|
||||
assert_eq!(true, i.can_attack());
|
||||
assert_eq!(true, i.can_be_attacked());
|
||||
i.mon.damage = i.mon.dex.new().hp;
|
||||
assert_eq!(false, i.alive());
|
||||
assert_eq!(false, i.can_attack());
|
||||
assert_eq!(false, i.can_be_attacked());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Move {
|
||||
Pass(usize),
|
||||
Attack(usize, usize),
|
||||
Swap(usize, usize),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Event {
|
||||
MustSwap(usize),
|
||||
Lose(usize),
|
||||
Win(usize),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod mon_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let mut my_mons = vec![
|
||||
mon::Instance::roll(mon::Dex::Pika),
|
||||
mon::Instance::roll(mon::Dex::Mari),
|
||||
];
|
||||
my_mons[0].hit(5);
|
||||
let they_mons = vec![
|
||||
mon::Instance::roll(mon::Dex::Mari),
|
||||
mon::Instance::roll(mon::Dex::Mari),
|
||||
mon::Instance::roll(mon::Dex::Mari),
|
||||
];
|
||||
|
||||
let mut engine = battle::Engine::new(my_mons, they_mons);
|
||||
|
||||
// team0mon0 trades attacks with team1mon0
|
||||
engine
|
||||
.enqueue(battle::Move::Attack(1, 0))
|
||||
.expect("failed to attack");
|
||||
engine
|
||||
.enqueue(battle::Move::Attack(0, 1))
|
||||
.expect("failed to attack");
|
||||
assert_eq!(0, engine.exec().len());
|
||||
assert_eq!(6, engine.teams()[0].mons()[0].damage());
|
||||
assert_eq!(0, engine.teams()[0].mons()[1].damage());
|
||||
assert_eq!(1, engine.teams()[1].mons()[0].damage());
|
||||
assert_eq!(0, engine.teams()[1].mons()[1].damage());
|
||||
assert_eq!(0, engine.teams()[1].mons()[2].damage());
|
||||
|
||||
// team0mon0 swaps with team0mon0
|
||||
// team1mon0 attacks team0
|
||||
engine
|
||||
.enqueue(battle::Move::Attack(1, 0))
|
||||
.expect("failed to attack");
|
||||
engine
|
||||
.enqueue(battle::Move::Swap(0, 1))
|
||||
.expect("failed to swap");
|
||||
assert_eq!(0, engine.exec().len());
|
||||
assert_eq!(6, engine.teams()[0].mons()[0].damage());
|
||||
assert_eq!(1, engine.teams()[0].mons()[1].damage());
|
||||
assert_eq!(1, engine.teams()[1].mons()[0].damage());
|
||||
assert_eq!(0, engine.teams()[1].mons()[1].damage());
|
||||
assert_eq!(0, engine.teams()[1].mons()[2].damage());
|
||||
|
||||
// team1mon0 wears down team0mon1
|
||||
for i in 2..engine.teams()[0].mons()[1].mon.dex.hp() {
|
||||
engine
|
||||
.enqueue(battle::Move::Attack(1, 0))
|
||||
.expect("failed to attack");
|
||||
assert_eq!(0, engine.exec().len());
|
||||
assert_eq!(i, engine.teams()[0].mons()[1].damage());
|
||||
}
|
||||
|
||||
// team1mon0 downs team0mon1
|
||||
engine
|
||||
.enqueue(battle::Move::Attack(1, 0))
|
||||
.expect("failed to kill");
|
||||
let turn_results = engine.exec();
|
||||
assert_eq!(1, turn_results.len());
|
||||
assert_eq!(battle::Event::MustSwap(0), turn_results[0]);
|
||||
assert_eq!(
|
||||
engine.teams()[0].mons()[1].mon.dex.hp(),
|
||||
engine.teams()[0].mons()[1].damage()
|
||||
);
|
||||
assert!(!engine.teams()[0].mons()[1].out);
|
||||
|
||||
// team0 must swap
|
||||
assert!(engine.enqueue(battle::Move::Attack(0, 1)).is_err());
|
||||
assert!(engine.enqueue(battle::Move::Pass(0)).is_err());
|
||||
engine
|
||||
.enqueue(battle::Move::Swap(0, 0))
|
||||
.expect("failed to swap on MustSwap");
|
||||
assert!(engine.enqueue(battle::Move::Attack(1, 0)).is_err());
|
||||
assert_eq!(0, engine.exec().len());
|
||||
|
||||
// team1mon0 wears down team0mon0
|
||||
for i in 7..engine.teams()[0].mons()[0].mon.dex.hp() {
|
||||
engine
|
||||
.enqueue(battle::Move::Attack(1, 0))
|
||||
.expect("failed to attack");
|
||||
assert_eq!(0, engine.exec().len());
|
||||
assert_eq!(i, engine.teams()[0].mons()[0].damage());
|
||||
}
|
||||
|
||||
// team1mon0 downs team0mon0
|
||||
// team0 loses
|
||||
// team1 wins
|
||||
engine
|
||||
.enqueue(battle::Move::Attack(1, 0))
|
||||
.expect("failed to kill");
|
||||
let turn_results = engine.exec();
|
||||
assert_eq!(2, turn_results.len(), "{:?}", turn_results);
|
||||
assert_eq!(
|
||||
battle::Event::Lose(0),
|
||||
turn_results[0],
|
||||
"{:?}",
|
||||
turn_results
|
||||
);
|
||||
assert_eq!(battle::Event::Win(1), turn_results[1], "{:?}", turn_results);
|
||||
assert_eq!(
|
||||
engine.teams()[0].mons()[0].mon.dex.hp(),
|
||||
engine.teams()[0].mons()[0].damage()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
mod main {
|
||||
use {super::data};
|
||||
|
||||
pub fn main() {
|
||||
let db = data::DB::new().expect("could not make db");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_main() {
|
||||
main();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod data {
|
||||
mod models {
|
||||
use {
|
||||
native_db::{
|
||||
native_db,
|
||||
ToKey,
|
||||
},
|
||||
native_model::{
|
||||
native_model,
|
||||
Model,
|
||||
},
|
||||
serde::{Deserialize, Serialize},
|
||||
};
|
||||
|
||||
pub mod v1 {
|
||||
use super::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
#[native_db]
|
||||
pub struct Person {
|
||||
#[primary_key]
|
||||
pub name: String,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use native_db::*;
|
||||
|
||||
pub type Person = models::v1::Person;
|
||||
|
||||
static MODELS: std::sync::LazyLock<Models> = std::sync::LazyLock::new(|| {
|
||||
let mut models = Models::new();
|
||||
models.define::<Person>().expect("failed to define person");
|
||||
models
|
||||
});
|
||||
|
||||
pub struct DB<'a> {
|
||||
db: Database<'a>,
|
||||
}
|
||||
|
||||
impl<'a> DB<'a> {
|
||||
pub fn new() -> Result<DB<'a>, String> {
|
||||
let db = match Builder::new().create_in_memory(&MODELS) {
|
||||
Ok(db) => Ok(db),
|
||||
Err(msg) => Err(msg.to_string()),
|
||||
}?;
|
||||
Ok(DB{
|
||||
db: db,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert<T: ToInput>(self: &DB<'a>, v: T) -> Result<(), String> {
|
||||
let t = match self.db.rw_transaction() {
|
||||
Ok(t) => t,
|
||||
Err(msg) => return Err(msg.to_string()),
|
||||
};
|
||||
if let Err(msg) = t.insert(v) {
|
||||
return Err(msg.to_string());
|
||||
}
|
||||
if let Err(msg) = t.commit() {
|
||||
return Err(msg.to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get<T: ToInput, S: ToKey>(self: &DB<'a>, primary: S) -> Result<Option<T>, String> {
|
||||
let t = match self.db.r_transaction() {
|
||||
Ok(t) => t,
|
||||
Err(msg) => return Err(msg.to_string()),
|
||||
};
|
||||
match t.get().primary(primary) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(msg) => Err(msg.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mvp() {
|
||||
let db = DB::new().expect("could not make db");
|
||||
let person = Person{name: "me".to_string()};
|
||||
db.insert(person.clone()).expect("could not insert person");
|
||||
assert_eq!(person, db.get("me".to_string()).expect("could not get").expect("got none"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
use godot::prelude::*;
|
||||
|
||||
struct MyExtension;
|
||||
|
||||
#[gdextension]
|
||||
unsafe impl ExtensionLibrary for MyExtension {
|
||||
}
|
||||
|
||||
pub mod player;
|
||||
pub mod mon;
|
||||
pub mod mon_move;
|
||||
@@ -1,74 +0,0 @@
|
||||
use godot::prelude::*;
|
||||
|
||||
use godot::classes::{ISprite2D, Sprite2D};
|
||||
#[derive(GodotClass, Debug)]
|
||||
#[class(base=Sprite2D)]
|
||||
pub struct Mon {
|
||||
base: Base<Sprite2D>,
|
||||
name: GString,
|
||||
hp: i32,
|
||||
damage: i32,
|
||||
level_d: i32,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl ISprite2D for Mon {
|
||||
fn init(base: Base<Sprite2D>) -> Self {
|
||||
Self{
|
||||
base: base,
|
||||
name: "my_name".into(),
|
||||
hp: 100,
|
||||
damage: 0,
|
||||
level_d: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn ready(&mut self) {
|
||||
self.hp_change()
|
||||
}
|
||||
}
|
||||
|
||||
use crate::mon_move;
|
||||
#[godot_api]
|
||||
impl Mon {
|
||||
#[func]
|
||||
pub fn mon_name(&self) -> GString {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
#[func]
|
||||
pub fn hp(&self) -> f32 {
|
||||
(self.hp - self.damage) as f32 / self.hp as f32
|
||||
}
|
||||
|
||||
#[func]
|
||||
pub fn tackled(&mut self) {
|
||||
self.recv(mon_move::TACKLE);
|
||||
self.hp_change()
|
||||
}
|
||||
|
||||
fn hp_change(&mut self) {
|
||||
self.base_mut().emit_signal("hp_changed", &[]);
|
||||
}
|
||||
|
||||
#[signal]
|
||||
fn hp_changed();
|
||||
}
|
||||
|
||||
impl Mon {
|
||||
fn recv(&mut self, m: mon_move::MonMove) {
|
||||
m.use_on(self);
|
||||
}
|
||||
|
||||
pub fn hit_for(&mut self, power: i32) {
|
||||
self.damage += power;
|
||||
}
|
||||
|
||||
pub fn damage_scalar(&self) -> f32 {
|
||||
let mut result = 1.0;
|
||||
for _ in 0..self.level_d {
|
||||
result /= 2.0;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
use crate::mon;
|
||||
|
||||
pub struct MonMove {
|
||||
power: i32,
|
||||
}
|
||||
|
||||
impl MonMove {
|
||||
const fn new(power: i32) -> Self {
|
||||
Self{power: power}
|
||||
}
|
||||
|
||||
pub fn use_on(&self, dest: &mut mon::Mon) {
|
||||
let power = self.power as f32 * dest.damage_scalar();
|
||||
dest.hit_for(power as i32);
|
||||
}
|
||||
}
|
||||
|
||||
pub const TACKLE: MonMove = MonMove::new(25);
|
||||
@@ -1,45 +0,0 @@
|
||||
use godot::prelude::*;
|
||||
|
||||
use godot::classes::{ISprite2D, Sprite2D};
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Sprite2D)]
|
||||
struct BreeLPlayer {
|
||||
base: Base<Sprite2D>,
|
||||
speed: f64,
|
||||
radians: f32,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl ISprite2D for BreeLPlayer {
|
||||
fn init(base: Base<Sprite2D>) -> Self {
|
||||
BreeLPlayer{base: base, speed: 200.0, radians: 1.0}
|
||||
}
|
||||
|
||||
fn physics_process(&mut self, delta: f64) {
|
||||
let radians = self.radians * delta as f32;
|
||||
self.base_mut().rotate(radians);
|
||||
|
||||
let rotation = self.base().get_rotation();
|
||||
let velocity = Vector2::UP.rotated(rotation) * self.speed as f32;
|
||||
self.base_mut().translate(velocity * delta as f32);
|
||||
|
||||
self.increase_speed(100.0 * delta as f64);
|
||||
self.radians *= 1.01 as f32;
|
||||
}
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl BreeLPlayer {
|
||||
#[func]
|
||||
fn increase_speed(&mut self, amount: f64) {
|
||||
self.speed += amount;
|
||||
self.base_mut().emit_signal("speed_increased", &[]);
|
||||
}
|
||||
|
||||
#[signal]
|
||||
fn speed_increased();
|
||||
|
||||
fn drop(&mut self) {
|
||||
eprintln!("drop");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user