File size: 12,582 Bytes
fe24641
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import json
import random
from typing import Dict, Any, List

class QwenTextGenerator:
    """Text generation using Qwen2.5-0.5B-Instruct for monster traits and dialogue"""
    
    def __init__(self, device: str = "cuda"):
        self.device = device if torch.cuda.is_available() else "cpu"
        self.model = None
        self.tokenizer = None
        self.model_id = "Qwen/Qwen2.5-0.5B-Instruct"
        
        # Generation parameters
        self.max_new_tokens = 150
        self.temperature = 0.8
        self.top_p = 0.9
        
        # Monster trait templates
        self.trait_categories = {
            'elements': ['fire', 'water', 'earth', 'wind', 'electric', 'ice', 'nature', 'dark', 'light', 'neutral'],
            'personalities': ['brave', 'timid', 'aggressive', 'gentle', 'playful', 'serious', 'loyal', 'independent', 'curious', 'protective'],
            'body_types': ['bipedal', 'quadruped', 'serpentine', 'avian', 'aquatic', 'insectoid', 'humanoid', 'amorphous'],
            'sizes': ['tiny', 'small', 'medium', 'large', 'giant'],
            'special_features': ['wings', 'horns', 'tail', 'spikes', 'fur', 'scales', 'armor', 'crystals', 'flames', 'aura']
        }
    
    def load_model(self):
        """Lazy load the text generation model"""
        if self.model is None:
            try:
                # Load tokenizer
                self.tokenizer = AutoTokenizer.from_pretrained(self.model_id)
                
                # Model configuration
                torch_dtype = torch.float16 if self.device == "cuda" else torch.float32
                
                self.model = AutoModelForCausalLM.from_pretrained(
                    self.model_id,
                    torch_dtype=torch_dtype,
                    device_map="auto" if self.device == "cuda" else None,
                    low_cpu_mem_usage=True
                )
                
                if self.device == "cpu":
                    self.model.to(self.device)
                
            except Exception as e:
                print(f"Failed to load text generation model: {e}")
                raise
    
    def generate_traits(self, description: str) -> Dict[str, Any]:
        """Generate monster traits from description"""
        try:
            self.load_model()
            
            # Create prompt for trait generation
            prompt = self._create_trait_prompt(description)
            
            # Generate response
            inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
            
            with torch.no_grad():
                outputs = self.model.generate(
                    **inputs,
                    max_new_tokens=self.max_new_tokens,
                    temperature=self.temperature,
                    top_p=self.top_p,
                    do_sample=True,
                    pad_token_id=self.tokenizer.eos_token_id
                )
            
            response = self.tokenizer.decode(outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True)
            
            # Parse traits from response
            traits = self._parse_traits(response, description)
            
            return traits
            
        except Exception as e:
            print(f"Error generating traits: {e}")
            return self._generate_fallback_traits(description)
    
    def generate_dialogue(self, traits: Dict[str, Any]) -> str:
        """Generate monster dialogue (emoji + numbers)"""
        try:
            # Create emoji dialogue based on personality and mood
            personality = traits.get('personality', 'neutral')
            
            # Emoji mapping for personalities
            emoji_map = {
                'brave': ['๐Ÿ’ช', '๐Ÿ”ฅ', 'โš”๏ธ', '๐Ÿ›ก๏ธ'],
                'timid': ['๐Ÿ˜ฐ', '๐Ÿฅบ', '๐Ÿ’ฆ', 'โ“'],
                'aggressive': ['๐Ÿ˜ค', '๐Ÿ’ข', '๐Ÿ”ฅ', 'โšก'],
                'gentle': ['๐Ÿ’š', '๐ŸŒธ', 'โœจ', '๐ŸŒŸ'],
                'playful': ['๐Ÿ˜Š', '๐ŸŽฎ', '๐ŸŽฏ', '๐ŸŽช'],
                'serious': ['๐Ÿค–', '๐Ÿ“Š', 'โšก', '๐Ÿ’ฏ'],
                'loyal': ['๐Ÿ’–', '๐Ÿค', '๐Ÿ›ก๏ธ', 'โญ'],
                'independent': ['๐Ÿš€', '๐ŸŒ', '๐Ÿ”ฎ', '๐Ÿ’ซ'],
                'curious': ['๐Ÿ”', 'โ“', '๐Ÿ’ก', '๐ŸŒŸ'],
                'protective': ['๐Ÿ›ก๏ธ', '๐Ÿ’ช', '๐Ÿฐ', 'โš”๏ธ']
            }
            
            # Get appropriate emojis
            emojis = emoji_map.get(personality, ['๐Ÿค–', '๐Ÿ’š', 'โœจ'])
            selected_emojis = random.sample(emojis, min(2, len(emojis)))
            
            # Generate status numbers (representing monster's current state)
            hp_percent = random.randint(70, 100)
            happiness = random.randint(60, 95)
            energy = random.randint(50, 90)
            
            # Create dialogue
            dialogue = f"{selected_emojis[0]}{selected_emojis[1] if len(selected_emojis) > 1 else '๐Ÿ’š'}"
            dialogue += f"{hp_percent}๏ธโƒฃ{happiness}๏ธโƒฃ"
            
            return dialogue
            
        except Exception as e:
            print(f"Error generating dialogue: {e}")
            return "๐Ÿค–๐Ÿ’š9๏ธโƒฃ0๏ธโƒฃ"
    
    def _create_trait_prompt(self, description: str) -> str:
        """Create prompt for trait generation"""
        prompt = f"""<|im_start|>system
You are a creative game designer creating unique digital monsters. Generate detailed traits for a monster based on the description.
<|im_end|>
<|im_start|>user
Create traits for this monster: {description}

Include: name, species, element, personality, appearance details, and special abilities.
<|im_end|>
<|im_start|>assistant
"""
        return prompt
    
    def _parse_traits(self, response: str, original_description: str) -> Dict[str, Any]:
        """Parse traits from model response"""
        traits = {
            'description': original_description,
            'raw_response': response
        }
        
        # Extract name
        if "name:" in response.lower():
            name_start = response.lower().find("name:") + 5
            name_end = response.find("\n", name_start)
            if name_end == -1:
                name_end = len(response)
            traits['name'] = response[name_start:name_end].strip()
        else:
            traits['name'] = self._generate_name()
        
        # Extract or assign element
        element_found = False
        for element in self.trait_categories['elements']:
            if element in response.lower():
                traits['element'] = element
                element_found = True
                break
        
        if not element_found:
            traits['element'] = random.choice(self.trait_categories['elements'])
        
        # Extract or assign personality
        personality_found = False
        for personality in self.trait_categories['personalities']:
            if personality in response.lower():
                traits['personality'] = personality
                personality_found = True
                break
        
        if not personality_found:
            traits['personality'] = random.choice(self.trait_categories['personalities'])
        
        # Extract appearance
        traits['appearance'] = self._extract_appearance(response)
        
        # Extract abilities
        traits['abilities'] = self._extract_abilities(response, traits['element'])
        
        # Add color scheme based on element
        traits['color_scheme'] = self._get_color_scheme(traits['element'])
        
        return traits
    
    def _generate_name(self) -> str:
        """Generate a random monster name"""
        prefixes = ['Pyro', 'Aqua', 'Terra', 'Aero', 'Volt', 'Cryo', 'Flora', 'Shadow', 'Lumi', 'Neo']
        suffixes = ['mon', 'beast', 'guard', 'wing', 'claw', 'fang', 'horn', 'tail', 'byte', 'spark']
        
        return random.choice(prefixes) + random.choice(suffixes)
    
    def _extract_appearance(self, response: str) -> str:
        """Extract appearance description"""
        appearance_keywords = ['appearance', 'looks like', 'resembles', 'body', 'color', 'size']
        
        for keyword in appearance_keywords:
            if keyword in response.lower():
                start = response.lower().find(keyword)
                end = response.find('.', start)
                if end == -1:
                    end = response.find('\n', start)
                if end == -1:
                    end = len(response)
                
                return response[start:end].strip()
        
        # Fallback appearance
        body_type = random.choice(self.trait_categories['body_types'])
        size = random.choice(self.trait_categories['sizes'])
        feature = random.choice(self.trait_categories['special_features'])
        
        return f"A {size} {body_type} creature with {feature}"
    
    def _extract_abilities(self, response: str, element: str) -> List[str]:
        """Extract or generate abilities"""
        abilities = []
        
        ability_keywords = ['ability', 'power', 'skill', 'can', 'capable']
        for keyword in ability_keywords:
            if keyword in response.lower():
                # Try to extract abilities from response
                start = response.lower().find(keyword)
                end = response.find('.', start)
                if end > start:
                    ability_text = response[start:end]
                    abilities.append(ability_text.strip())
        
        # If no abilities found, generate based on element
        if not abilities:
            element_abilities = {
                'fire': ['Flame Burst', 'Heat Wave', 'Ember Shield'],
                'water': ['Aqua Jet', 'Bubble Shield', 'Tidal Wave'],
                'earth': ['Rock Throw', 'Earthquake', 'Stone Armor'],
                'wind': ['Gust', 'Tornado', 'Wind Shield'],
                'electric': ['Thunder Shock', 'Static Field', 'Lightning Speed'],
                'ice': ['Ice Beam', 'Frost Armor', 'Blizzard'],
                'nature': ['Vine Whip', 'Healing Bloom', 'Nature\'s Guard'],
                'dark': ['Shadow Strike', 'Dark Pulse', 'Void Shield'],
                'light': ['Light Beam', 'Healing Light', 'Radiant Shield'],
                'neutral': ['Tackle', 'Defense Curl', 'Focus']
            }
            
            abilities = random.sample(
                element_abilities.get(element, element_abilities['neutral']), 
                2
            )
        
        return abilities
    
    def _get_color_scheme(self, element: str) -> str:
        """Get color scheme based on element"""
        color_schemes = {
            'fire': 'red and orange with yellow accents',
            'water': 'blue and cyan with white highlights',
            'earth': 'brown and green with stone textures',
            'wind': 'white and light blue with swirling patterns',
            'electric': 'yellow and blue with sparking effects',
            'ice': 'light blue and white with crystalline features',
            'nature': 'green and brown with leaf patterns',
            'dark': 'black and purple with shadow effects',
            'light': 'white and gold with glowing aura',
            'neutral': 'gray and silver with balanced tones'
        }
        
        return color_schemes.get(element, 'varied colors with unique patterns')
    
    def _generate_fallback_traits(self, description: str) -> Dict[str, Any]:
        """Generate fallback traits if model fails"""
        element = random.choice(self.trait_categories['elements'])
        personality = random.choice(self.trait_categories['personalities'])
        
        return {
            'name': self._generate_name(),
            'species': 'Digital Monster',
            'element': element,
            'personality': personality,
            'appearance': f"A unique {random.choice(self.trait_categories['sizes'])} digital creature",
            'color_scheme': self._get_color_scheme(element),
            'abilities': self._extract_abilities("", element),
            'description': description
        }
    
    def to(self, device: str):
        """Move model to specified device"""
        self.device = device
        if self.model:
            self.model.to(device)
    
    def __del__(self):
        """Cleanup when object is destroyed"""
        if self.model:
            del self.model
        if self.tokenizer:
            del self.tokenizer
        torch.cuda.empty_cache()