From b6d6adc3721b3cf9fd0626de6823a23ab90d912c Mon Sep 17 00:00:00 2001 From: Jobin Babu Ayathil Date: Thu, 11 Jul 2024 16:17:51 +0400 Subject: [PATCH] Update BattleshipGame.sol --- battleship-game/contracts/BattleshipGame.sol | 363 ++++++++++++------- 1 file changed, 224 insertions(+), 139 deletions(-) diff --git a/battleship-game/contracts/BattleshipGame.sol b/battleship-game/contracts/BattleshipGame.sol index d18f0c0..8e38c37 100644 --- a/battleship-game/contracts/BattleshipGame.sol +++ b/battleship-game/contracts/BattleshipGame.sol @@ -1,146 +1,231 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -contract BattleshipGame { - uint8 constant gridSize = 100; - uint8 constant totalShips = 249; - uint8 constant shipLength = 3; - - struct Position { - uint8 x; - uint8 y; - } - - struct Ship { - Position start; - bool[shipLength] hits; - } - - Ship[totalShips] public ships; - mapping(uint16 => uint8) private positionToShipIndex; - mapping(uint16 => bool) public hits; - uint256 private seed; - uint256 private nonce = 0; - uint256 public prizePool; - bool[totalShips] public graveyard; - uint8 public sunkShipsCount; - bool public gameOver; - - Position[] public hitPositions; - - constructor() { - seed = uint256(keccak256(abi.encodePacked(block.difficulty))); - generatePositions(); - } - - function generatePositions() private { - uint8 index = 0; - while (index < totalShips) { - uint256 hash = uint256(keccak256(abi.encodePacked(seed, nonce))); - for (uint8 i = 0; i < 36 && index < totalShips; i++) { - uint8 x = uint8(hash & 0x7F) % gridSize; - uint8 y = uint8((hash >> 7) & 0x7F) % gridSize; - - if (isPositionUniqueAndFits(x, y)) { - ships[index].start = Position(x, y); - for (uint8 j = 0; j < shipLength; j++) { - uint16 positionKey = packCoordinates(x + j, y); - positionToShipIndex[positionKey] = index; - } - index++; +contract Battleship { + /// @notice The size of the game grid + uint8 constant gridSize = 100; + + /// @notice The total number of ships in the game + uint8 constant totalShips = 249; + + /// @notice The length of each ship + uint8 constant shipLength = 3; + + /// @notice The address of the contract owner + address public owner; + + /// @notice Mapping to store admin addresses + mapping(address => bool) public admins; + + /// @notice Structure representing a position on the grid + struct Position { + uint8 x; + uint8 y; + } + + /// @notice Structure representing a ship + struct Ship { + Position start; + bool[shipLength] hits; + } + + /// @notice Array to store all ships + Ship[totalShips] private ships; + + /// @notice Mapping to store the index of ships based on their position + mapping(uint16 => uint8) private positionToShipIndex; + + /// @notice Mapping to store hit positions + mapping(uint16 => bool) public hits; + + /// @notice Array to store hit coordinates + Position[] public hitCoordinates; + + /// @notice Seed for random position generation + uint256 private seed; + + /// @notice Nonce for random position generation + uint256 private nonce = 0; + + /// @notice Prize pool amount in ether + uint256 public prizePool; + + /// @notice Array to store the status of ships (sunk or not) + bool[totalShips] public graveyard; + + /// @notice Count of sunk ships + uint8 public sunkShipsCount; + + /// @notice Game over flag + bool public gameOver; + + /// @notice Constructor to initialize the contract + constructor() { + seed = uint256(keccak256(abi.encodePacked(block.difficulty))); + owner = msg.sender; + admins[owner] = true; + generatePositions(); + } + + /// @notice Modifier to restrict function access to admins only + modifier onlyAdmin() { + require(admins[msg.sender], "Caller is not an admin"); + _; + } + + /// @notice Modifier to restrict function access to the owner only + modifier onlyOwner() { + require(msg.sender == owner, "Caller is not the owner"); + _; + } + + /// @notice Add a new admin + /// @param newAdmin The address of the new admin + function addAdmin(address newAdmin) public onlyOwner { + admins[newAdmin] = true; + } + + /// @notice Generate random positions for the ships + function generatePositions() private { + uint8 index = 0; + while (index < totalShips) { + uint256 hash = uint256(keccak256(abi.encodePacked(seed, nonce))); + for (uint8 i = 0; i < 36 && index < totalShips; i++) { + uint8 x = uint8(hash & 0x7F) % gridSize; + uint8 y = uint8((hash >> 7) & 0x7F) % gridSize; + + if (isPositionUniqueAndFits(x, y)) { + ships[index].start = Position(x, y); + for (uint8 j = 0; j < shipLength; j++) { + uint16 positionKey = packCoordinates(x + j, y); + positionToShipIndex[positionKey] = index; + } + index++; + } + hash >>= 14; + } + nonce++; } - hash >>= 14; - } - nonce++; - } - } - - function isPositionUniqueAndFits(uint8 x, uint8 y) private view returns (bool) { - if (x + shipLength > gridSize) return false; - for (uint8 j = 0; j < shipLength; j++) { - uint16 positionKey = packCoordinates(x + j, y); - if (positionToShipIndex[positionKey] != 0) { - return false; - } - } - return true; - } - - function packCoordinates(uint8 x, uint8 y) private pure returns (uint16) { - return (uint16(x) << 8) | uint16(y); - } - - function getShipPosition(uint8 shipIndex) public view returns (Position memory) { - require(shipIndex < totalShips, 'Ship index out of bounds'); - return ships[shipIndex].start; - } - - function getAllShipPositions() public view returns (Ship[totalShips] memory) { - return ships; - } - - function getShipAtPosition(uint8 x, uint8 y) public view returns (uint8) { - uint16 positionKey = packCoordinates(x, y); - uint8 shipIndex = positionToShipIndex[positionKey]; - require(shipIndex != 0, 'No ship at given position'); - return shipIndex; - } - - function hit(uint8 x, uint8 y) public payable { - require(!gameOver, 'Game is over, no more hits accepted'); - require(msg.value == 0.0443 ether, 'Incorrect fee amount'); - uint16 positionKey = packCoordinates(x, y); - require(!hits[positionKey], 'Cell already hit'); - - prizePool += msg.value; - hits[positionKey] = true; - hitPositions.push(Position(x, y)); - - uint8 shipIndex = positionToShipIndex[positionKey]; - if (shipIndex != 0) { - Ship storage ship = ships[shipIndex]; - uint8 hitIndex = x - ship.start.x; - ship.hits[hitIndex] = true; - - bool allHit = true; - for (uint8 i = 0; i < shipLength; i++) { - if (!ship.hits[i]) { - allHit = false; - break; + } + + /// @notice Check if a position is unique and fits within the grid + /// @param x The x-coordinate + /// @param y The y-coordinate + /// @return bool Returns true if the position is unique and fits, false otherwise + function isPositionUniqueAndFits(uint8 x, uint8 y) private view returns (bool) { + if (x + shipLength > gridSize) return false; + for (uint8 j = 0; j < shipLength; j++) { + uint16 positionKey = packCoordinates(x + j, y); + if (positionToShipIndex[positionKey] != 0) { + return false; + } } - } - if (allHit) { - graveyard[shipIndex] = true; - sunkShipsCount++; - if (sunkShipsCount == totalShips) { - gameOver = true; + return true; + } + + /// @notice Pack coordinates into a single uint16 value + /// @param x The x-coordinate + /// @param y The y-coordinate + /// @return uint16 The packed coordinates + function packCoordinates(uint8 x, uint8 y) private pure returns (uint16) { + return uint16(x) << 8 | uint16(y); + } + + /// @notice Get the position of a ship + /// @param shipIndex The index of the ship + /// @return Position The position of the ship + function getShipPosition(uint8 shipIndex) public view onlyAdmin returns (Position memory) { + require(shipIndex < totalShips, "Ship index out of bounds"); + return ships[shipIndex].start; + } + + /// @notice Get positions of all ships + /// @return Ship[totalShips] An array of all ships + function getAllShipPositions() public view onlyAdmin returns (Ship[totalShips] memory) { + return ships; + } + + /// @notice Get the index of the ship at a given position + /// @param x The x-coordinate + /// @param y The y-coordinate + /// @return uint8 The index of the ship + function getShipAtPosition(uint8 x, uint8 y) public view onlyAdmin returns (uint8) { + uint16 positionKey = packCoordinates(x, y); + uint8 shipIndex = positionToShipIndex[positionKey]; + require(shipIndex != 0, "No ship at given position"); + return shipIndex; + } + + /// @notice Register a hit at a given position + /// @param x The x-coordinate + /// @param y The y-coordinate + function hit(uint8 x, uint8 y) public payable { + require(!gameOver, "Game is over, no more hits accepted"); + require(msg.value == 0.0443 ether, "Incorrect fee amount"); + uint16 positionKey = packCoordinates(x, y); + require(!hits[positionKey], "Cell already hit"); + + prizePool += msg.value; + hits[positionKey] = true; + hitCoordinates.push(Position(x, y)); + + uint8 shipIndex = positionToShipIndex[positionKey]; + if (shipIndex != 0) { + Ship storage ship = ships[shipIndex]; + uint8 hitIndex = x - ship.start.x; + ship.hits[hitIndex] = true; + + bool allHit = true; + for (uint8 i = 0; i < shipLength; i++) { + if (!ship.hits[i]) { + allHit = false; + break; + } + } + if (allHit) { + graveyard[shipIndex] = true; + sunkShipsCount++; + if (sunkShipsCount == totalShips) { + gameOver = true; + } + } } - } - } - } - - function isHit(uint8 x, uint8 y) public view returns (bool) { - uint16 positionKey = packCoordinates(x, y); - return hits[positionKey]; - } - - function isSunk(uint8 shipIndex) public view returns (bool) { - require(shipIndex < totalShips, 'Ship index out of bounds'); - return graveyard[shipIndex]; - } - - function getHitsOnShip(uint8 shipIndex) public view returns (bool[shipLength] memory) { - require(shipIndex < totalShips, 'Ship index out of bounds'); - return ships[shipIndex].hits; - } - - function getGraveyard() public view returns (bool[totalShips] memory) { - return graveyard; - } - - /// @notice Gets all hit positions on the grid. - /// @return An array of Position structs representing the hit positions. - function getAllHits() public view returns (Position[] memory) { - return hitPositions; - } + } + + /// @notice Check if a position has been hit + /// @param x The x-coordinate + /// @param y The y-coordinate + /// @return bool Returns true if the position has been hit, false otherwise + function isHit(uint8 x, uint8 y) public view returns (bool) { + uint16 positionKey = packCoordinates(x, y); + return hits[positionKey]; + } + + /// @notice Check if a ship is sunk + /// @param shipIndex The index of the ship + /// @return bool Returns true if the ship is sunk, false otherwise + function isSunk(uint8 shipIndex) public view returns (bool) { + require(shipIndex < totalShips, "Ship index out of bounds"); + return graveyard[shipIndex]; + } + + /// @notice Get the hit status of a ship + /// @param shipIndex The index of the ship + /// @return bool[shipLength] An array representing the hit status of the ship + function getHitsOnShip(uint8 shipIndex) public view onlyAdmin returns (bool[shipLength] memory) { + require(shipIndex < totalShips, "Ship index out of bounds"); + return ships[shipIndex].hits; + } + + /// @notice Get the status of all ships in the graveyard + /// @return bool[totalShips] An array representing the status of all ships + function getGraveyard() public view returns (bool[totalShips] memory) { + return graveyard; + } + + /// @notice Get all hit coordinates + /// @return Position[] An array of all hit coordinates + function getAllHits() public view returns (Position[] memory) { + return hitCoordinates; + } }