entropy_game/entity/
guest.rs

1use axum::async_trait;
2use sea_orm::{
3    entity::prelude::*, ActiveValue::NotSet, Condition, DatabaseTransaction, IntoActiveModel, Set,
4    Unchanged,
5};
6use serde::{Deserialize, Serialize};
7
8use crate::err::{ModelError, OperationError};
9use entropy_base::grid::{navi, FlatID, Node, NodeID};
10
11use super::{node, variant::DetectedGuest};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]
14#[sea_orm(table_name = "guest")]
15pub struct Model {
16    #[sea_orm(primary_key)]
17    pub id: i32,
18    pub energy: i64,
19    #[sea_orm(index)]
20    #[serde(
21        serialize_with = "entropy_base::grid::ser_flat",
22        deserialize_with = "entropy_base::grid::de_flat"
23    )]
24    pub pos: i32,
25    pub temperature: i16, // should be i8, but sea_orm always error
26    #[sea_orm(index)]
27    pub master_id: i32,
28}
29
30#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
31pub enum Relation {
32    #[sea_orm(
33        belongs_to = "super::player::Entity",
34        from = "Column::MasterId",
35        to = "super::player::Column::Id"
36    )]
37    Player,
38    #[sea_orm(
39        belongs_to = "super::node::Entity",
40        from = "Column::Pos",
41        to = "super::node::Column::Id"
42    )]
43    Node,
44}
45
46impl Related<super::player::Entity> for Entity {
47    fn to() -> RelationDef {
48        Relation::Player.def()
49    }
50}
51
52impl Related<super::node::Entity> for Entity {
53    fn to() -> RelationDef {
54        Relation::Node.def()
55    }
56}
57
58#[async_trait]
59impl ActiveModelBehavior for ActiveModel {
60    async fn before_save<C>(self, db: &C, _insert: bool) -> Result<Self, DbErr>
61    where
62        C: ConnectionTrait,
63    {
64        let pos = self.pos.as_ref();
65        node::Model::_ensure(db, FlatID::from(pos.clone()))
66            .await
67            .map_err(|e| DbErr::Custom(e.to_string()))?;
68        Ok(self)
69    }
70}
71
72impl Model {
73    pub async fn spawn<C: ConnectionTrait>(
74        db: &C,
75        pos: NodeID,
76        master_id: i32,
77    ) -> Result<Model, OperationError> {
78        let g = ActiveModel {
79            id: NotSet,
80            energy: Set(0),
81            pos: Set(pos.into_i32()),
82            temperature: Set(0),
83            master_id: Set(master_id),
84        };
85        Ok(g.insert(db).await?)
86    }
87
88    pub async fn walk_free<C: ConnectionTrait>(
89        &self,
90        db: &C,
91        to: navi::Direction,
92    ) -> Result<Model, OperationError> {
93        self.verify_energy(1)?;
94
95        let at = FlatID::from(self.pos).into_node_id().navi_to(to);
96        let mut g = self.into_active_model();
97        g.pos = Set(at.into_i32());
98        g.energy = Set(self.energy - 1);
99        Ok(g.update(db).await?)
100    }
101
102    /// Introduce a new guest from an existing guest,
103    /// transfer energy from the old to new.
104    ///
105    /// Return the new guest model.
106    ///
107    /// This method will not take any energy cost, it's FREE
108    pub async fn arrange_free(
109        &self,
110        txn: &DatabaseTransaction,
111        transfer_energy: i64,
112    ) -> Result<Model, OperationError> {
113        self.consume_energy(txn, transfer_energy).await?;
114
115        let to = ActiveModel {
116            energy: Set(transfer_energy),
117            pos: Set(self.pos),
118            temperature: Set(0),
119            master_id: Set(self.master_id),
120            ..Default::default()
121        };
122
123        let to = to.insert(txn).await?;
124
125        Ok(to)
126    }
127
128    pub async fn detect<C: ConnectionTrait>(
129        &self,
130        db: &C,
131    ) -> Result<Vec<DetectedGuest>, OperationError> {
132        let gs = Entity::find()
133            .filter(
134                Condition::all()
135                    .add(Column::Id.ne(self.id))
136                    .add(Column::Pos.eq(self.pos)),
137            )
138            .into_partial_model::<DetectedGuest>()
139            .all(db)
140            .await?;
141        Ok(gs)
142    }
143
144    /// Consume energy of self energy and update database.
145    pub async fn consume_energy<C: ConnectionTrait>(
146        &self,
147        db: &C,
148        energy: i64,
149    ) -> Result<Model, OperationError> {
150        self.verify_energy(energy)?;
151
152        let mut g = self.into_active_model();
153        g.energy = Set(self.energy - energy);
154        let g: Model = g.update(db).await?;
155
156        Ok(g)
157    }
158
159    pub fn get_efficiency(&self, cell: i8) -> f32 {
160        entropy_base::heat::carnot_efficiency(self.temperature as i8, cell)
161    }
162
163    // generate two middle model, handler use these model to do things left
164    pub fn _harvest_active_model(
165        self,
166        node: Node,
167        cell_i: usize,
168    ) -> Result<(self::ActiveModel, node::ActiveModel), ModelError> {
169        let mut data = node.data.clone();
170        let mut cell = node.data.get(cell_i).ok_or(ModelError::Parse {
171            desc: format!(
172                "request length({1}) out of range <- node({0:?})",
173                node.id, cell_i
174            ),
175        })?;
176
177        let mut g = self.into_active_model();
178
179        // Calculate the delta energy first
180        let temp = self.temperature as i8;
181        let delta = temp.abs_diff(cell);
182        let delta = (self.get_efficiency(cell) * delta as f32).div_euclid(2.0) as u8;
183
184        // no overflow will happen, the efficiency proves that, so no need to check
185
186        // Determine which temperature is hotter and colder.
187        // and go change
188        if temp > cell {
189            g.temperature = Set(temp.saturating_sub_unsigned(delta) as i16);
190            cell = cell.saturating_add_unsigned(delta);
191        } else if temp < cell {
192            g.temperature = Set(temp.saturating_add_unsigned(delta) as i16);
193            cell = cell.saturating_sub_unsigned(delta);
194        } else {
195            ()
196        };
197        g.energy = Set(self.energy + delta as i64);
198        data.set(cell_i, cell);
199        let n = node::ActiveModel {
200            id: Unchanged(node.id.into_i32()),
201            data: Set(data.into()),
202        };
203        Ok((g, n))
204    }
205
206    /// Check if Guest has enough energy.
207    ///
208    /// Return Ok(()) if energy is enough
209    /// Return Err if energy is not enough
210    fn verify_energy(&self, require: i64) -> Result<(), OperationError> {
211        if self.energy >= require {
212            Ok(())
213        } else {
214            Err(OperationError::EnergyNotEnough {
215                require: require,
216                reserve: self.energy,
217            })
218        }
219    }
220}