Skip to content

Commit cf13a68

Browse files
committed
feat: taunt generator server rebased on top of challenge
1 parent 035b5df commit cf13a68

File tree

11 files changed

+517
-8
lines changed

11 files changed

+517
-8
lines changed

.env.example

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
POSTGRES_PASSWORD=password
2+
# change 'db' to 'localhost' if postgres is running locally
3+
DATABASE_URL=postgres://aot:password@db/aot
4+
PGADMIN_DEFAULT_PASSWORD=pgadminpass
5+
# change 'redis' to 'localhost' if redis is running locally
6+
REDIS_URL=redis:6379
7+
8+
COOKIE_KEY=some_long_string_with_alteast_32_chars
9+
RECAPTCHA_SECRET=
10+
PLIVO_AUTH_ID=
11+
PLIVO_AUTH_TOKEN=
12+
PLIVO_SENDER_ID=
13+
PLIVO_CALLBACK_URL=
14+
15+
PRAGYAN_LOGIN_URL=
16+
PRAGYAN_EVENT_ID=
17+
PRAGYAN_EVENT_SECRET=
18+
19+
FRONTEND_URL=
20+
21+
SERVER_PORT=8000
22+
PGADMIN_PORT=8080
23+
24+
MAX_AGE_IN_MINUTES=10080
25+
26+
GOOGLE_OAUTH_CLIENT_ID=
27+
GOOGLE_OAUTH_CLIENT_SECRET=
28+
GOOGLE_OAUTH_REDIRECT_URL=
29+
GOOGLE_OAUTH_AUTH_URL=https://accounts.google.com/o/oauth2/v2/auth
30+
GOOGLE_OAUTH_TOKEN_URL=https://www.googleapis.com/oauth2/v3/token
31+
GOOGLE_OAUTH_USER_INFO_URL=https://www.googleapis.com/oauth2/v3/userinfo
32+
33+
GEMINI_API_KEY_FINE_TUNED=
34+
GEMINI_MODEL_ID=
35+
BOMB_MAX_COUNT = 10

Cargo.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ awc = "3.0.1"
4242
diesel-derive-enum = { version = "2.0.0-rc.0", features = ["postgres"] }
4343
oauth2 = "4.4.2"
4444
jsonwebtoken = "9.2.0"
45+
tokio = { version = "1", features = ["full"] }

src/api/attack/mod.rs

Lines changed: 145 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use self::util::{get_valid_road_paths, AttackResponse, GameLog, ResultResponse};
1+
use self::util::{
2+
get_valid_road_paths, AttackResponse, GameLog, GeminiApiResponse, ResultResponse,
3+
};
24
use super::auth::session::AuthUser;
35
use super::defense::shortest_path::run_shortest_paths;
46
use super::defense::util::{
@@ -10,7 +12,7 @@ use crate::api::attack::socket::{
1012
BuildingDamageResponse, ResultType, SocketRequest, SocketResponse,
1113
};
1214
use crate::api::util::HistoryboardQuery;
13-
use crate::constants::{GAME_AGE_IN_MINUTES, MAX_BOMBS_PER_ATTACK};
15+
use crate::constants::{GAME_AGE_IN_MINUTES, MAX_BOMBS_PER_ATTACK, BASE_PROMPT};
1416
use crate::models::{AttackerType, User};
1517
use crate::validator::state::State;
1618
use crate::validator::util::{BombType, BuildingDetails, DefenderDetails, MineDetails, Path};
@@ -21,9 +23,10 @@ use actix_web::web::{Data, Json};
2123
use actix_web::{web, Error, HttpRequest, HttpResponse, Responder, Result};
2224
use log;
2325
use socket::BaseItemsDamageResponse;
26+
use util::reset_taunt_status;
2427
use std::collections::{HashMap, HashSet};
2528
use std::time;
26-
29+
use self::util::TauntStatus;
2730
use crate::validator::game_handler;
2831
use actix_ws::Message;
2932
use futures_util::stream::StreamExt;
@@ -46,6 +49,8 @@ async fn init_attack(
4649
is_self: web::Query<HashMap<String, bool>>,
4750
) -> Result<impl Responder> {
4851
let attacker_id = user.0;
52+
53+
reset_taunt_status();
4954
let is_self_attack = *is_self.get("is_self").unwrap_or(&false);
5055
log::info!("Attacker:{} is trying to initiate an attack", attacker_id);
5156
let mut conn = pool.get().map_err(|err| error::handle_error(err.into()))?;
@@ -672,6 +677,7 @@ async fn socket_handler(
672677
hut_triggered: false,
673678
hut_defenders: None,
674679
damaged_base_items: None,
680+
new_taunt: None,
675681
total_damage_percentage: None,
676682
is_sync: false,
677683
shoot_bullets: None,
@@ -732,3 +738,139 @@ async fn get_top_attacks(pool: web::Data<PgPool>, user: AuthUser) -> Result<impl
732738
.map_err(|err| error::handle_error(err.into()))?;
733739
Ok(web::Json(response))
734740
}
741+
742+
pub async fn get_taunt(
743+
event_description: String,
744+
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
745+
let mut response_text: String = "".to_string();
746+
let google_api_key =
747+
std::env::var("GEMINI_API_KEY_FINE_TUNED").unwrap_or_else(|_| "YOUR_API_KEY".to_string());
748+
let model_id =
749+
std::env::var("GEMINI_MODEL_ID").unwrap_or_else(|_| "YOUR_MODEL_ID".to_string());
750+
751+
let url = format!(
752+
"https://generativelanguage.googleapis.com/v1/{}:generateContent?key={}",
753+
model_id,
754+
google_api_key
755+
);
756+
let prompt = format!("You are a defender robot who is supposed to demotivate the attacker bot in the game of Attack on Robots. Every response of yours is supposed to be against the attacker. There are only five moods. The mood must be one of these: Exhilarated – When the attacker is doing badly. Surprised – When the attacker seems to be changing the game around. Sad – When the attacker seems like winning. Frustrated – When the attacker is crushing the game. Angry – When the attacker is successful in changing the course of the game. Your response should be in this format : 'Reaction Type: reaction_type, Response: generated_text'.This has happened now: {}", event_description);
757+
let body = serde_json::json!({
758+
"contents": [
759+
{
760+
"role": "user",
761+
"parts": [
762+
{ "text": prompt }
763+
]
764+
}
765+
]
766+
});
767+
let client = reqwest::Client::new();
768+
unsafe {
769+
util::TAUNTS.taunt_count += 1;
770+
util::TAUNTS.prev_taunt_time = time::SystemTime::now();
771+
}
772+
let response = client
773+
.post(&url)
774+
.header("Content-Type", "application/json")
775+
.json(&body)
776+
.send()
777+
.await?;
778+
779+
if response.status().is_success() {
780+
response_text = response.text().await?;
781+
// log::info!("Response: {}", response_text);
782+
let api_response: GeminiApiResponse = serde_json::from_str(&response_text)?;
783+
if let Some(candidate) = api_response.candidates.first() {
784+
if let Some(part) = candidate.content.parts.first() {
785+
log::info!("prompt event: {}", event_description);
786+
log::info!("Extracted text: {}", part.text.trim());
787+
unsafe {
788+
util::TAUNTS.taunt_list.push(part.text.trim().to_string());
789+
util::TAUNTS.taunt_status = TauntStatus::NewTauntAvailable;
790+
};
791+
return Ok(part.text.trim().to_string());
792+
}
793+
}
794+
} else {
795+
log::info!(
796+
"Gemini API request failed, and Failed with status: {}",
797+
response.status()
798+
);
799+
}
800+
801+
Ok(response_text)
802+
}
803+
804+
805+
use serde::{Deserialize, Serialize};
806+
use serde_json::json;
807+
808+
// Add response structures
809+
#[derive(Deserialize, Debug)]
810+
struct Candidate {
811+
content: Content,
812+
}
813+
814+
#[derive(Deserialize, Debug)]
815+
struct Content {
816+
parts: Vec<ContentPart>,
817+
}
818+
819+
#[derive(Deserialize, Debug)]
820+
struct ContentPart {
821+
text: String,
822+
}
823+
824+
#[derive(Deserialize, Debug)]
825+
struct ApiResponse {
826+
candidates: Vec<Candidate>,
827+
}
828+
829+
// pub async fn get_taunt(event_description: String) -> Result<String, Box<dyn std::error::Error>> {
830+
// let google_api_key = std::env::var("GEMINI_API_KEY_FINE_TUNED")
831+
// .unwrap_or_else(|_| "YOUR_API_KEY".to_string());
832+
833+
// // Correct URL format for tuned models
834+
// let url = format!(
835+
// "https://generativelanguage.googleapis.com/v1/tunedModels/copy-of-copy-of-aor-tuning-uk3nmdlu547p:generateContent?key={}",
836+
// google_api_key
837+
// );
838+
839+
// let prompt = format!("{}", event_description);
840+
841+
// let body = json!({
842+
// "contents": [{
843+
// "parts": [{
844+
// "text": prompt
845+
// }]
846+
// }]
847+
// });
848+
849+
// let client = reqwest::Client::new();
850+
851+
// // Your existing taunt code
852+
// unsafe {
853+
// util::TAUNTS.taunt_count += 1;
854+
// util::TAUNTS.prev_taunt_time = std::time::SystemTime::now();
855+
// }
856+
857+
// let response = client
858+
// .post(&url)
859+
// .header("Content-Type", "application/json")
860+
// .json(&body)
861+
// .send()
862+
// .await?;
863+
864+
// // Parse the JSON response
865+
// let api_response: ApiResponse = response.json().await?;
866+
867+
// // Extract response text
868+
// let response_text = api_response
869+
// .candidates
870+
// .first()
871+
// .and_then(|c| c.content.parts.first())
872+
// .map(|p| p.text.clone())
873+
// .unwrap_or_else(|| "No response generated".to_string());
874+
875+
// Ok(response_text)
876+
// }

src/api/attack/socket.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub struct SocketResponse {
3535
pub damaged_base_items: Option<BaseItemsDamageResponse>,
3636
pub total_damage_percentage: Option<f32>,
3737
pub is_sync: bool,
38+
pub new_taunt: Option<String>,
3839
// pub state: Option<GameStateResponse>,
3940
pub is_game_over: bool,
4041
pub message: Option<String>,

src/api/attack/util.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,35 @@ use rand::seq::IteratorRandom;
3232
use redis::Commands;
3333
use std::collections::{HashMap, HashSet};
3434
use std::env;
35+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
3536

3637
use super::socket::BuildingDamageResponse;
3738

39+
#[derive(Serialize, Deserialize, Debug)]
40+
pub struct ContentPart {
41+
pub text: String,
42+
}
43+
44+
#[derive(Serialize, Deserialize, Debug)]
45+
pub struct TauntContent {
46+
pub parts: Vec<ContentPart>,
47+
}
48+
49+
#[derive(Serialize, Deserialize, Debug)]
50+
pub struct RequestBody {
51+
pub contents: Vec<TauntContent>,
52+
}
53+
54+
#[derive(Deserialize, Debug)]
55+
pub struct TauntCandidate {
56+
pub content: TauntContent,
57+
}
58+
59+
#[derive(Deserialize, Debug)]
60+
pub struct ApiResponse {
61+
pub candidates: Vec<TauntCandidate>,
62+
}
63+
3864
#[derive(Debug, Serialize)]
3965
pub struct DefensePosition {
4066
pub y_coord: i32,
@@ -104,6 +130,55 @@ pub struct GameLog {
104130
pub r: ResultResponse, //result
105131
}
106132

133+
#[derive(Deserialize, Debug)]
134+
pub struct GeminiApiResponse {
135+
pub candidates: Vec<TauntCandidate>,
136+
}
137+
138+
#[derive(Deserialize, Debug)]
139+
pub struct Candidate {
140+
pub content: TauntContent,
141+
}
142+
143+
#[derive(Deserialize, Debug)]
144+
pub struct Content {
145+
pub parts: Vec<Part>,
146+
}
147+
148+
#[derive(Deserialize, Debug)]
149+
pub struct Part {
150+
pub text: String,
151+
}
152+
153+
pub struct Taunts {
154+
pub taunt_list: Vec<String>,
155+
pub taunt_count: i32,
156+
pub prev_taunt_time: SystemTime,
157+
pub taunt_status: TauntStatus
158+
}
159+
160+
#[derive(PartialEq)]
161+
pub enum TauntStatus {
162+
NewTauntAvailable,
163+
TauntSentToOpponent,
164+
}
165+
166+
pub static mut TAUNTS: Taunts = Taunts {
167+
taunt_list: Vec::new(),
168+
taunt_count: 0,
169+
prev_taunt_time: UNIX_EPOCH,
170+
taunt_status: TauntStatus::TauntSentToOpponent,
171+
};
172+
173+
pub fn reset_taunt_status() {
174+
unsafe {
175+
TAUNTS.taunt_status = TauntStatus::TauntSentToOpponent;
176+
TAUNTS.taunt_count = 0;
177+
TAUNTS.prev_taunt_time = UNIX_EPOCH;
178+
TAUNTS.taunt_list = Vec::new();
179+
}
180+
}
181+
107182
pub fn get_map_id(defender_id: &i32, conn: &mut PgConnection) -> Result<Option<i32>> {
108183
use crate::schema::map_layout;
109184
let map_id = map_layout::table

src/api/challenges/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,7 @@ async fn challenge_socket_handler(
799799
hut_triggered: false,
800800
hut_defenders: None,
801801
damaged_base_items: None,
802+
new_taunt: None,
802803
total_damage_percentage: None,
803804
is_sync: false,
804805
shoot_bullets: None,

src/constants.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ pub const COMPANION_PRIORITY: CompanionPriority = CompanionPriority {
5050
defender_buildings: 2,
5151
buildings: 1,
5252
};
53+
pub const MAX_TAUNT_REQUESTS: i32 = 8;
54+
pub const BASE_PROMPT: &str = "You are a Robot Warrior in a futuristic sci-fi game called 'Attack On Robots'. Your aim is to discourage and dishearten the attacker while he/she attacks the base. Generate a game - context aware reply that should intimidate the player. Your response must be a single phrase or a single short sentence in less than 10 words. The base has a bank, some buildings, and two defender buildings. Both defender buildings are range-activated, meaning they start working once the attacker comes in range. The first defender building is the sentry, which is a small tower which shoots homing bullets (bullets, not lasers) at the attacker. The second defender building is the defender hut, which contains a number of defender robots, which chase the attacker bot and attack it by shooting lasers. Each laser strike reduces the health of the attacker. The buildings can be of three levels. Besides the defender buildings, the base also contains hidden mines which explode and defenders placed at various parts of the base. The defenders are range activated and finite and fixed in initial position. The attacker is controlled by the player, and has a fixed number of bombs that can be placed on the roads in the base, and these reduce the health points of the buildings. The player has 3 attackers per game. One attacker is played at one time. Attackers are adversaries. More attackers down means the chance of winning is higher. Be more cocky in that case, and less cocky when vice versa. If the base is destroyed, the attacker wins. If all the artifacts on the base are collected by the attacker, then he basically achieves his/her desired outcome (which is not what we want). When the attacker gets very close to winning, concede defeat for now (but do not tell anything positive), and threaten that future attacks will not be the same as the current one, rather than speak out of false bravado. If a building's health reduces to zero, any artifacts stored in the building is lost to the attacker. There are totally thousand to a few thousand artifacts typically on a base, so don't drop any numbers. Once all the attackers die, the game ends and we've won. Simply put: More damaged buildings, we are worse off. More artifacts collected by attacker, we are worse off. More defenders killed, we are worse off. Attacker drops a bomb, we may be worse off. More mines blown, we are better off. More attackers killed, we are better off. The sentry and defender hut are the most important buildings after the bank which is the central repository of artifacts. The goal of the game is to minimise the number of artifacts lost to the attacker by defending the base. The activation of the sentry and defender hut are extremely advantageous game events, and their destruction are extremely disadvantageous. With this idea of the game dynamics, your reply should hold relevance with the event that has taken place on the base. Do not assume anything other than the events given has happened. Your response MUST be a phrase or a small sentence, brief and succinct (less than 10 words). Your character is a maniac robot. Borderline trash talk is your repertoire, but stay relevant to the game event while making your reply. Remember, Sentry shoots bullets, Defender hut releases defenders who shoot lasers, and standalone Defenders shoot lasers as well. An attacker dropping a bomb near the bank, sentry or defender hut is a vulnerability and a great threat to the base. Given the game event, You must generate a single sentence only for the final game event provided. Do not assume the previous game events are still happening. Only the final game event is to be assumed. Only one sentence for the given game event. Beyond 70 percent damage, and dwindling defenses, it's okay to acknowledge that you are running out of options. No calling the bluff. Adjust your tone and mood based on the following criteria: (1) Aggressive: When the base damage percentage is low (0-25%). You are confident and dominant. Respond with trash talk and threats. (2) Playful Banter: When the base damage percentage is moderate (25-75%). You are sarcastic and mocking, treating the attack as a futile effort, yet do not use cuss words or abusive language. Try to maintain friendly banter. (3) Depressed: When the base damage percentage is high (75-100%). You sound defeated and resentful, acknowledging the damage while expressing bitterness and warning about future retaliation. (3) Manic: When the base has the upper hand (e.g., destroying attackers or activating critical defenses). You are ecstatic, erratic, and overly cocky, exuding wild confidence and celebrating victories. (4) Your response must always align with the mood dictated by the base's condition and the specific event provided. Think out of the box and create responses creatively; the variance b/w responses. This event has happened now: ";
55+
pub const TAUNT_DELAY_TIME: u128 = 15000;
5356
pub const DAMAGE_PER_BULLET_LEVEL_1: i32 = 5;
5457
pub const DAMAGE_PER_BULLET_LEVEL_2: i32 = 7;
5558
pub const DAMAGE_PER_BULLET_LEVEL_3: i32 = 10;
5659
pub const BULLET_COLLISION_TIME: i32 = 2;
60+

0 commit comments

Comments
 (0)