// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** * TokenHunterGame * เกมอาณาจักรนักล่าโทเค็น (The Token Hunter Kingdom) * * รวม 3 ระบบไว้ในสัญญาเดียว * 1. เหรียญ THK - รางวัลจากการกำจัดมอนสเตอร์ * 2. คลังไอเทม - ซื้อ / ขายคืน / รับจากการดรอป * 3. กระดานจัดอันดับ - จำนวนมอนสเตอร์ที่กำจัดสะสม * * หมายเหตุ: สัญญาเก็บเฉพาะ "ใครเป็นเจ้าของอะไรกี่ชิ้น" และ "ราคา" เท่านั้น * ส่วนค่าสถานะของไอเทม (พลังโจมตี เกราะ ความเร็ว ฯลฯ) เก็บไว้ฝั่ง Unity * เพื่อให้ปรับสมดุลได้โดยไม่ต้องดีพลอยสัญญาใหม่ */ contract TokenHunterGame { // =============================================================== // ส่วนที่ 1 : เหรียญ THK // =============================================================== string public name = "Token Hunter Coin"; string public symbol = "THK"; uint8 public decimals = 0; address public owner; mapping(address => uint256) public balanceOf; event Reward(address indexed player, uint256 amount); event Spend(address indexed player, uint256 amount); // =============================================================== // ส่วนที่ 2 : ไอเทม // =============================================================== struct Item { string itemName; uint256 price; // ราคาซื้อ uint256 refund; // ราคาขายคืน bool exists; } mapping(uint256 => Item) public itemCatalog; uint256[] public itemIds; // ที่อยู่ผู้เล่น => รหัสไอเทม => จำนวนที่ครอบครอง mapping(address => mapping(uint256 => uint256)) public inventoryOf; event ItemBought(address indexed player, uint256 indexed itemId, uint256 price); event ItemSold(address indexed player, uint256 indexed itemId, uint256 refund); event ItemDropped(address indexed player, uint256 indexed itemId); event ItemAdded(uint256 indexed itemId, string itemName, uint256 price); event ItemPriceUpdated(uint256 indexed itemId, uint256 price, uint256 refund); // =============================================================== // ส่วนที่ 3 : กระดานจัดอันดับ // =============================================================== mapping(address => uint256) public killsOf; address[] public players; mapping(address => bool) private isRegistered; event KillsSubmitted(address indexed player, uint256 amount, uint256 total); // =============================================================== // ตัวช่วย // =============================================================== modifier onlyOwner() { require(msg.sender == owner, "Not the owner"); _; } constructor() { owner = msg.sender; // ---------- อาวุธ 101-199 ---------- _addItem(101, "Wooden Sword", 40, 20); _addItem(102, "Iron Sword", 100, 50); _addItem(103, "Dragon Sword", 200, 100); // ---------- เกราะ 201-299 ---------- _addItem(201, "Leather Armor", 40, 20); _addItem(202, "Iron Armor", 100, 50); _addItem(203, "Dragon Armor", 200, 100); // ---------- หมวก 301-399 ---------- _addItem(301, "Cloth Hat", 40, 20); _addItem(302, "Iron Helmet", 100, 50); _addItem(303, "Hunter Crown", 200, 100); // ---------- รองเท้า 401-499 ---------- _addItem(401, "Cloth Boots", 40, 20); _addItem(402, "Wind Boots", 100, 50); _addItem(403, "Divine Boots", 200, 100); // ---------- เครื่องประดับ 501-599 ---------- _addItem(501, "Feather Ring", 40, 20); _addItem(502, "Gale Necklace", 100, 50); _addItem(503, "Hunter Wings", 200, 100); // ---------- สกิล 601-699 ---------- _addItem(601, "Water Skill", 80, 40); _addItem(602, "Fire Skill", 150, 75); _addItem(603, "Thunder Skill", 250, 125); } // =============================================================== // เหรียญ // =============================================================== /// ผู้เล่นเรียกเองเพื่อรับรางวัลที่สะสมไว้ระหว่างเล่น function rewardPlayer(uint256 amount) external { require(amount > 0, "Amount must be positive"); balanceOf[msg.sender] += amount; emit Reward(msg.sender, amount); } /// ใช้เหรียญโดยตรง เช่น ซื้อขวดยาที่ไม่เก็บเข้าคลัง function spendToken(uint256 amount) external { require(balanceOf[msg.sender] >= amount, "Not enough THK"); balanceOf[msg.sender] -= amount; emit Spend(msg.sender, amount); } function getBalance(address player) external view returns (uint256) { return balanceOf[player]; } // =============================================================== // ไอเทม // =============================================================== /// เจ้าของสัญญาเพิ่มไอเทมใหม่ (ใช้เพิ่มดาบเล่มใหม่ในอนาคต) function addItem( uint256 itemId, string calldata itemName, uint256 price, uint256 refund ) external onlyOwner { _addItem(itemId, itemName, price, refund); } /// เจ้าของสัญญาแก้ราคาไอเทมที่มีอยู่แล้ว (ใช้ปรับสมดุลราคา) function updateItemPrice( uint256 itemId, uint256 price, uint256 refund ) external onlyOwner { require(itemCatalog[itemId].exists, "Item not found"); require(refund <= price, "Refund cannot exceed price"); itemCatalog[itemId].price = price; itemCatalog[itemId].refund = refund; emit ItemPriceUpdated(itemId, price, refund); } function _addItem( uint256 itemId, string memory itemName, uint256 price, uint256 refund ) internal { require(!itemCatalog[itemId].exists, "Item ID already used"); require(refund <= price, "Refund cannot exceed price"); itemCatalog[itemId] = Item(itemName, price, refund, true); itemIds.push(itemId); emit ItemAdded(itemId, itemName, price); } /// ซื้อไอเทม : หักเหรียญแล้วเพิ่มของเข้าคลัง function buyItem(uint256 itemId) external { Item memory item = itemCatalog[itemId]; require(item.exists, "Item not found"); require(balanceOf[msg.sender] >= item.price, "Not enough THK"); balanceOf[msg.sender] -= item.price; inventoryOf[msg.sender][itemId] += 1; emit Spend(msg.sender, item.price); emit ItemBought(msg.sender, itemId, item.price); } /// ขายไอเทมคืน : ลดของในคลังแล้วคืนเหรียญ function sellItem(uint256 itemId) external { Item memory item = itemCatalog[itemId]; require(item.exists, "Item not found"); require(inventoryOf[msg.sender][itemId] > 0, "You do not own this item"); inventoryOf[msg.sender][itemId] -= 1; balanceOf[msg.sender] += item.refund; emit Reward(msg.sender, item.refund); emit ItemSold(msg.sender, itemId, item.refund); } /// อ่านคลังของผู้เล่นทั้งหมดในครั้งเดียว function getInventory(address player) external view returns (uint256[] memory ids, uint256[] memory counts) { ids = new uint256[](itemIds.length); counts = new uint256[](itemIds.length); for (uint256 i = 0; i < itemIds.length; i++) { ids[i] = itemIds[i]; counts[i] = inventoryOf[player][itemIds[i]]; } } function getItemCount() external view returns (uint256) { return itemIds.length; } // =============================================================== // กระดานจัดอันดับ // =============================================================== function submitKills(uint256 amount) external { require(amount > 0, "Amount must be positive"); _addKills(msg.sender, amount); } function _addKills(address player, uint256 amount) internal { if (!isRegistered[player]) { isRegistered[player] = true; players.push(player); } killsOf[player] += amount; emit KillsSubmitted(player, amount, killsOf[player]); } /// อ่านกระดานจัดอันดับทั้งหมด (ยังไม่เรียงลำดับ ให้ฝั่งเกมเรียงเอง) function getLeaderboard() external view returns (address[] memory addresses, uint256[] memory kills) { addresses = new address[](players.length); kills = new uint256[](players.length); for (uint256 i = 0; i < players.length; i++) { addresses[i] = players[i]; kills[i] = killsOf[players[i]]; } } function getPlayerCount() external view returns (uint256) { return players.length; } // =============================================================== // เคลมรวมตอนจบด่าน (ธุรกรรมเดียว ประหยัดค่าแก๊ส) // =============================================================== /** * เรียกเมื่อกดปุ่ม CLAIM ที่หน้า LEVEL CLEAR * tokenAmount - เหรียญที่สะสมได้ระหว่างเล่น * killAmount - จำนวนมอนสเตอร์ที่กำจัดได้ * droppedItemIds - รหัสไอเทมที่บอสดรอป (ส่งอาร์เรย์ว่างถ้าไม่มี) */ function claimAll( uint256 tokenAmount, uint256 killAmount, uint256[] calldata droppedItemIds ) external { if (tokenAmount > 0) { balanceOf[msg.sender] += tokenAmount; emit Reward(msg.sender, tokenAmount); } if (killAmount > 0) { _addKills(msg.sender, killAmount); } for (uint256 i = 0; i < droppedItemIds.length; i++) { uint256 itemId = droppedItemIds[i]; require(itemCatalog[itemId].exists, "Dropped item not found"); inventoryOf[msg.sender][itemId] += 1; emit ItemDropped(msg.sender, itemId); } } }