@@ -36,10 +36,18 @@ contract GameCharacter is ERC721, Ownable {
3636 // ============================================
3737
3838 event CharacterMinted (
39- address indexed owner , uint256 indexed tokenId , uint256 level , uint256 strength , uint256 health
39+ address indexed owner ,
40+ uint256 indexed tokenId ,
41+ uint256 level ,
42+ uint256 strength ,
43+ uint256 health
4044 );
4145 event CharacterLeveledUp (uint256 indexed tokenId , uint256 newLevel );
42- event ExperienceGained (uint256 indexed tokenId , uint256 xpGained , uint256 totalXP );
46+ event ExperienceGained (
47+ uint256 indexed tokenId ,
48+ uint256 xpGained ,
49+ uint256 totalXP
50+ );
4351
4452 // ============================================
4553 // CONSTRUCTOR
@@ -60,18 +68,21 @@ contract GameCharacter is ERC721, Ownable {
6068 _nextTokenId++ ;
6169
6270 tokenAttributes[tokenId] = CharacterAttributes ({
63- level: 1 , strength: _pseudoRandom (10 , 20 , tokenId), health: _pseudoRandom (50 , 100 , tokenId), experience: 0
71+ level: 1 ,
72+ strength: _pseudoRandom (10 , 20 , tokenId),
73+ health: _pseudoRandom (50 , 100 , tokenId),
74+ experience: 0
6475 });
6576
66- _safeMint (to, tokenId);
67-
6877 emit CharacterMinted (
6978 to,
7079 tokenId,
7180 tokenAttributes[tokenId].level,
7281 tokenAttributes[tokenId].strength,
7382 tokenAttributes[tokenId].health
7483 );
84+
85+ _safeMint (to, tokenId);
7586 }
7687
7788 // ============================================
@@ -132,45 +143,48 @@ contract GameCharacter is ERC721, Ownable {
132143 * @param tokenId The token to generate an image for
133144 * @return SVG image as a string
134145 */
135- function generateCharacterImage (uint256 tokenId ) public view returns (string memory ) {
146+ function generateCharacterImage (
147+ uint256 tokenId
148+ ) public view returns (string memory ) {
136149 CharacterAttributes memory attrs = tokenAttributes[tokenId];
137150
138151 // Choose color based on level
139152 string memory color = _getColorForLevel (attrs.level);
140153
141154 // Build the SVG
142- return string (
143- abi.encodePacked (
144- '<svg xmlns="http://www.w3.org/2000/svg" width="350" height="350" style="background:#1a1a2e"> ' ,
145- // Character body (a simple rectangle that changes color by level)
146- '<rect x="125" y="120" width="100" height="150" fill=" ' ,
147- color,
148- '" stroke="#fff" stroke-width="3" rx="10"/> ' ,
149- // Head
150- '<circle cx="175" cy="80" r="35" fill=" ' ,
151- color,
152- '" stroke="#fff" stroke-width="3"/> ' ,
153- // Eyes
154- '<circle cx="165" cy="75" r="5" fill="#fff"/> ' ,
155- '<circle cx="185" cy="75" r="5" fill="#fff"/> ' ,
156- // Level badge
157- '<rect x="15" y="15" width="80" height="40" fill="#e94560" rx="8"/> ' ,
158- '<text x="55" y="42" font-family="Arial" font-size="20" fill="#fff" text-anchor="middle" font-weight="bold">LVL ' ,
159- attrs.level.toString (),
160- "</text> " ,
161- // Stats display
162- '<text x="175" y="300" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle">STR: ' ,
163- attrs.strength.toString (),
164- "</text> " ,
165- '<text x="175" y="320" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle">HP: ' ,
166- attrs.health.toString (),
167- "</text> " ,
168- '<text x="175" y="340" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle">XP: ' ,
169- attrs.experience.toString (),
170- "</text> " ,
171- "</svg> "
172- )
173- );
155+ return
156+ string (
157+ abi.encodePacked (
158+ '<svg xmlns="http://www.w3.org/2000/svg" width="350" height="350" style="background:#1a1a2e"> ' ,
159+ // Character body (a simple rectangle that changes color by level)
160+ '<rect x="125" y="120" width="100" height="150" fill=" ' ,
161+ color,
162+ '" stroke="#fff" stroke-width="3" rx="10"/> ' ,
163+ // Head
164+ '<circle cx="175" cy="80" r="35" fill=" ' ,
165+ color,
166+ '" stroke="#fff" stroke-width="3"/> ' ,
167+ // Eyes
168+ '<circle cx="165" cy="75" r="5" fill="#fff"/> ' ,
169+ '<circle cx="185" cy="75" r="5" fill="#fff"/> ' ,
170+ // Level badge
171+ '<rect x="15" y="15" width="80" height="40" fill="#e94560" rx="8"/> ' ,
172+ '<text x="55" y="42" font-family="Arial" font-size="20" fill="#fff" text-anchor="middle" font-weight="bold">LVL ' ,
173+ attrs.level.toString (),
174+ "</text> " ,
175+ // Stats display
176+ '<text x="175" y="300" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle">STR: ' ,
177+ attrs.strength.toString (),
178+ "</text> " ,
179+ '<text x="175" y="320" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle">HP: ' ,
180+ attrs.health.toString (),
181+ "</text> " ,
182+ '<text x="175" y="340" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle">XP: ' ,
183+ attrs.experience.toString (),
184+ "</text> " ,
185+ "</svg> "
186+ )
187+ );
174188 }
175189
176190 /**
@@ -180,43 +194,51 @@ contract GameCharacter is ERC721, Ownable {
180194 * @param tokenId The token to generate metadata for
181195 * @return JSON metadata as a string
182196 */
183- function generateMetadata (uint256 tokenId ) public view returns (string memory ) {
197+ function generateMetadata (
198+ uint256 tokenId
199+ ) public view returns (string memory ) {
184200 CharacterAttributes memory attrs = tokenAttributes[tokenId];
185201
186202 // Generate the SVG image
187203 string memory svg = generateCharacterImage (tokenId);
188204
189205 // Encode SVG to Base64
190- string memory imageURI = string (abi.encodePacked ("data:image/svg+xml;base64, " , Base64.encode (bytes (svg))));
191-
192- // Build JSON metadata
193- return string (
206+ string memory imageURI = string (
194207 abi.encodePacked (
195- "{ " ,
196- '"name": "GameCharacter # ' ,
197- tokenId.toString (),
198- '", ' ,
199- '"description": "A dynamic game character that grows stronger with training!", ' ,
200- '"image": " ' ,
201- imageURI,
202- '", ' ,
203- '"attributes": [ ' ,
204- '{"trait_type": "Level", "value": ' ,
205- attrs.level.toString (),
206- "}, " ,
207- '{"trait_type": "Strength", "value": ' ,
208- attrs.strength.toString (),
209- "}, " ,
210- '{"trait_type": "Health", "value": ' ,
211- attrs.health.toString (),
212- "}, " ,
213- '{"trait_type": "Experience", "value": ' ,
214- attrs.experience.toString (),
215- "} " ,
216- "] " ,
217- "} "
208+ "data:image/svg+xml;base64, " ,
209+ Base64.encode (bytes (svg))
218210 )
219211 );
212+
213+ // Build JSON metadata
214+ return
215+ string (
216+ abi.encodePacked (
217+ "{ " ,
218+ '"name": "GameCharacter # ' ,
219+ tokenId.toString (),
220+ '", ' ,
221+ '"description": "A dynamic game character that grows stronger with training!", ' ,
222+ '"image": " ' ,
223+ imageURI,
224+ '", ' ,
225+ '"attributes": [ ' ,
226+ '{"trait_type": "Level", "value": ' ,
227+ attrs.level.toString (),
228+ "}, " ,
229+ '{"trait_type": "Strength", "value": ' ,
230+ attrs.strength.toString (),
231+ "}, " ,
232+ '{"trait_type": "Health", "value": ' ,
233+ attrs.health.toString (),
234+ "}, " ,
235+ '{"trait_type": "Experience", "value": ' ,
236+ attrs.experience.toString (),
237+ "} " ,
238+ "] " ,
239+ "} "
240+ )
241+ );
220242 }
221243
222244 /**
@@ -226,12 +248,20 @@ contract GameCharacter is ERC721, Ownable {
226248 * @param tokenId The token to get the URI for
227249 * @return The full token URI (a data URI with base64-encoded JSON)
228250 */
229- function tokenURI (uint256 tokenId ) public view override returns (string memory ) {
251+ function tokenURI (
252+ uint256 tokenId
253+ ) public view override returns (string memory ) {
230254 require (_ownerOf (tokenId) != address (0 ), "Token does not exist " );
231255
232256 string memory json = generateMetadata (tokenId);
233257
234- return string (abi.encodePacked ("data:application/json;base64, " , Base64.encode (bytes (json))));
258+ return
259+ string (
260+ abi.encodePacked (
261+ "data:application/json;base64, " ,
262+ Base64.encode (bytes (json))
263+ )
264+ );
235265 }
236266
237267 // ============================================
@@ -242,15 +272,19 @@ contract GameCharacter is ERC721, Ownable {
242272 * @dev Get color based on character level
243273 * Higher level = cooler colors!
244274 */
245- function _getColorForLevel (uint256 level ) internal pure returns (string memory ) {
275+ function _getColorForLevel (
276+ uint256 level
277+ ) internal pure returns (string memory ) {
246278 if (level >= 10 ) return "#ffd700 " ; // Gold
247279 if (level >= 7 ) return "#9b59b6 " ; // Purple
248280 if (level >= 5 ) return "#3498db " ; // Blue
249281 if (level >= 3 ) return "#2ecc71 " ; // Green
250282 return "#95a5a6 " ; // Gray (level 1-2)
251283 }
252284
253- function getCharacterAttributes (uint256 tokenId ) public view returns (CharacterAttributes memory ) {
285+ function getCharacterAttributes (
286+ uint256 tokenId
287+ ) public view returns (CharacterAttributes memory ) {
254288 require (_ownerOf (tokenId) != address (0 ), "Token does not exist " );
255289 return tokenAttributes[tokenId];
256290 }
@@ -260,10 +294,16 @@ contract GameCharacter is ERC721, Ownable {
260294 }
261295
262296 // slither-disable-start weak-prng
263- function _pseudoRandom (uint256 min , uint256 max , uint256 seed ) private view returns (uint256 ) {
297+ function _pseudoRandom (
298+ uint256 min ,
299+ uint256 max ,
300+ uint256 seed
301+ ) private view returns (uint256 ) {
264302 // We use block.prevrandao (The Beacon Chain randomness)
265303 // We removed block.timestamp to reduce miner influence vectors
266- uint256 randomHash = uint256 (keccak256 (abi.encodePacked (block .prevrandao , msg .sender , seed)));
304+ uint256 randomHash = uint256 (
305+ keccak256 (abi.encodePacked (block .prevrandao , msg .sender , seed))
306+ );
267307
268308 return min + (randomHash % (max - min + 1 ));
269309 }
0 commit comments