---
# System prepended metadata

title: 311159023徐若曦_區塊鏈設計入門期末報告

---

# 311159023徐若曦_區塊鏈設計入門期末報告
## Remix IDE
### Solidity
#### conctracts/game v7.sol
##### 程式設計
```
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Game {
    uint8 private targetNumber;
    uint8 private targetSuit;
    uint256 private pool;
    address[] private players;
    mapping(address => uint8) private guessCountByAddress;
    mapping(address => Guess[]) private guessHistoryByAddress;
    address public owner;
    uint8 public reductionPercentage;

    struct Guess {
        uint8 number;
        uint8 suit;
    }

    event AllPlayers(address[] players);
    event AllPlayersCount(uint256 playersCount);
    event AllPlayersGuessCount(uint256 playersGuessCount);
    event DuplicateGuess(address indexed player, uint8 number, uint8 suit);
    event GameCorrect(address indexed player, uint8 number, uint8 suit, uint256 reward);
    event GuessIncorrect(address indexed player, uint8 number, uint8 suit, uint256 remainingReward);
    event RevealTarget(uint8 targetNumber, uint8 targetSuit); 
    event ReductionPercentageUpdated(uint8 newReductionPercentage);

    modifier isOwner() {
        require(msg.sender == owner, "Caller is not owner");
        _;
    }

    constructor(uint8 _targetNumber, uint8 _targetSuit, uint8 _initialReductionPercentage) {
        require(_targetNumber >= 1 && _targetNumber <= 13, "Number must be between 1 and 13");
        require(_targetSuit >= 1 && _targetSuit <= 4, "Suit must be between 1 and 4");
        require(_initialReductionPercentage <= 10, "Reduction percentage cannot exceed 10");

        owner = msg.sender;
        targetNumber = _targetNumber;
        targetSuit = _targetSuit;
        reductionPercentage = _initialReductionPercentage;
    }

    function getAllPlayers() public isOwner {
        emit AllPlayers(players);
    }

    function getAllPlayersCount() public isOwner {
        emit AllPlayersCount(players.length);
    }

    function getAllPlayersGuessCount() public isOwner {
        uint256 totalGuessCount = 0;

        for (uint256 i = 0; i < players.length; i++) {
            totalGuessCount += guessCountByAddress[players[i]];
        }
        emit AllPlayersGuessCount(totalGuessCount);
    }

    function getGuessCount() public view returns (uint8) {
        return guessCountByAddress[msg.sender];
    }

    function getGuessHistory() public view returns (uint8[] memory, uint8[] memory){
        Guess[] storage history = guessHistoryByAddress[msg.sender];
        uint8[] memory numbers = new uint8[](history.length);
        uint8[] memory suits = new uint8[](history.length);

        for (uint256 i = 0; i < history.length; i++) {
            numbers[i] = history[i].number;
            suits[i] = history[i].suit;
        }
        return (numbers, suits);
    }

    function guess(uint8 _number, uint8 _suit) public payable {
        require(_number >= 1 && _number <= 13, "Number must be between 1 and 13");
        require(_suit >= 1 && _suit <= 4, "Suit must be between 1 and 4");
        require(msg.value == 1 ether, "You must send exactly 1 ETH");

        Guess[] storage history = guessHistoryByAddress[msg.sender];

        for (uint256 i = 0; i < history.length; i++) {
            if (history[i].number == _number && history[i].suit == _suit) {
                emit DuplicateGuess(msg.sender, _number, _suit);
                return;
            }
        }

        if (guessCountByAddress[msg.sender] == 0) {
            players.push(msg.sender);
        }

        pool += msg.value;
        guessCountByAddress[msg.sender]++;
        guessHistoryByAddress[msg.sender].push(Guess({number: _number, suit: _suit}));

        uint256 reward = calculateReward();

        if (_number == targetNumber && _suit == targetSuit) {
            uint256 ownerShare = pool - reward;
            
            pool = 0;

            payable(msg.sender).transfer(reward);
            payable(owner).transfer(ownerShare);

            resetGame();

            emit GameCorrect(msg.sender, targetNumber, targetSuit, reward);
        } else {
            emit GuessIncorrect(msg.sender, _number, _suit, reward);
        }
    }

    function revealTarget() public isOwner {
        emit RevealTarget(targetNumber, targetSuit);
    }
        
    function setNewTarget(uint8 _newTargetNumber, uint8 _newTargetSuit) public isOwner {
        require(_newTargetNumber >= 1 && _newTargetNumber <= 13, "Number must be between 1 and 13");
        require(_newTargetSuit >= 1 && _newTargetSuit <= 4, "Suit must be between 1 and 4");

        targetNumber = _newTargetNumber;
        targetSuit = _newTargetSuit;
    }

    function setReductionPercentage(uint8 _reductionPercentage) public isOwner {
        require(_reductionPercentage <= 10, "Reduction percentage cannot exceed 10");
        
        reductionPercentage = _reductionPercentage;
        
        emit ReductionPercentageUpdated(_reductionPercentage);
    }

    function calculateReward() internal view returns (uint256) {
        uint256 totalReduction = reductionPercentage * guessCountByAddress[msg.sender];

        if (totalReduction > 90) {
            totalReduction = 90;
        }
        return pool * (100 - totalReduction) / 100;
    }

    function resetGame() internal {
        for (uint256 i = 0; i < players.length; i++) {
            address player = players[i];
            delete guessCountByAddress[player];
            delete guessHistoryByAddress[player];
        }
        delete players;
    }
}
```
### TypeScript
#### scripts/deploy v7.ts
##### 程式設計
```
import { ethers } from 'ethers';
import { deploy } from './ethers-lib';

async function main() {
    const initialTargetNumber = Math.floor(Math.random() * 13) + 1;
    const initialTargetSuit = Math.floor(Math.random() * 4) + 1;
    const initialReductionPercentage = 5;

    const gameContract = await deploy('Game', [
        initialTargetNumber,
        initialTargetSuit,
        initialReductionPercentage,
    ]);
    console.log(`Game contract deployed at address: ${gameContract.address}`);
    console.log(`Initial reduction percentage set to: ${initialReductionPercentage}%`);

    gameContract.on("AllPlayers", (players) => {
        console.log("All players emitted:");
        players.forEach((player: string, index: number) => {
            console.log(`Player ${index + 1}: ${player}`);
        });
    });

    gameContract.on("AllPlayersCount", (count) => {
        console.log(`Total Players Count: ${count}`);
    });

    gameContract.on("AllPlayersGuessCount", (totalGuessCount) => {
        console.log(`Total Players Guess Count: ${totalGuessCount}`);
    });

    gameContract.on("DuplicateGuess", (player, number, suit) => {
        console.log(`Duplicate guess detected from ${player}`);
        console.log(`Guess: Number ${number}, Suit ${suit}`);
    });

    gameContract.on("GameCorrect", async (player, number, suit, reward) => {
        console.log(`Correct guess by ${player}`);
        console.log(`Guess: Number ${number}, Suit ${suit}`);
        console.log(`Reward: ${ethers.utils.formatEther(reward)} ETH`);

        const newTargetNumber = Math.floor(Math.random() * 13) + 1;
        const newTargetSuit = Math.floor(Math.random() * 4) + 1;

        await gameContract.setNewTarget(newTargetNumber, newTargetSuit);
    });

    gameContract.on("GuessIncorrect", (player, number, suit, remainingReward) => {
        console.log(`Incorrect guess by ${player}`);
        console.log(`Guess: Number ${number}, Suit ${suit}`);
        console.log(`Remaining potential reward: ${ethers.utils.formatEther(remainingReward)} ETH`);
    });

    gameContract.on("RevealTarget", (number, suit) => {
        console.log(`Target card revealed: Number ${number}, Suit ${suit}`);
    });

    gameContract.on("ReductionPercentageUpdated", (newReductionPercentage) => {
        console.log(`Reduction percentage updated to: ${newReductionPercentage}%`);
    });
}

main().catch((error) => {
    console.error("Error in script execution:", error.message);
});
```
## Front-end
### HTML
#### index v5.html
##### 程式設計
```
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Blockchain Card Guessing Game</title>
    <link rel="stylesheet" href="style v5.css">
    <script src="https://cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.umd.min.js"></script>
    <script src="script v5.js" defer></script>
</head>

<body>
    <div class="game-container">
        <header>
            <h1>Blockchain Card Guessing Game</h1>
            <p>Guess the target card (number and suit) to win rewards!</p>
        </header>

        <section class="guess-form">
            <h2>Make a Guess</h2>
            <label for="number">Card Number (1-13):</label>
            <input type="number" id="number" placeholder="Enter a number (1-13)" min="1" max="13">

            <label for="suit">Card Suit (1-4):</label>
            <input type="number" id="suit" placeholder="Enter a suit (1-4)" min="1" max="4">

            <button id="guessButton">Submit Guess (1 ETH)</button>
        </section>

        <section class="event-log">
            <h2>Game Events</h2>
            <ul id="eventLog"></ul>
        </section>

        <section class="owner-actions">
            <h2>Owner Controls</h2>
            <button id="revealButton">Reveal Target Card</button>
        </section>
    </div>
</body>

</html>
```
### CSS
#### style v5.css
##### 程式設計
```
body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f0f4f8;
    color: #333;
}

.game-container {
    max-width: 800px;
    margin: 50px auto;
    padding: 20px;
    background-color: #fff;
    border-radius: 8px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

header {
    text-align: center;
    margin-bottom: 20px;
}

header h1 {
    color: #4caf50;
    margin: 0;
}

header p {
    color: #666;
    margin: 10px 0 20px;
}

section {
    margin-bottom: 30px;
}

h2 {
    border-bottom: 2px solid #4caf50;
    padding-bottom: 5px;
    margin-bottom: 15px;
    color: #4caf50;
}

label {
    display: block;
    margin-top: 10px;
    font-weight: bold;
}

input {
    width: 100%;
    padding: 8px;
    margin: 5px 0 15px;
    border: 1px solid #ccc;
    border-radius: 4px;
    box-sizing: border-box;
}

button {
    background-color: #4caf50;
    color: white;
    padding: 10px 15px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 0.3s;
}

button:hover {
    background-color: #45a049;
}

.event-log ul {
    list-style: none;
    padding: 0;
}

.event-log li {
    margin: 10px 0;
    padding: 10px;
    background-color: #f9f9f9;
    border: 1px solid #ddd;
    border-radius: 4px;
}
```
### JavaScript
#### script v5.js
##### 程式設計
```
const contractAddress = "YOUR_CONTRACT_ADDRESS";
const contractABI = [

];

document.addEventListener("DOMContentLoaded", () => {
    let provider, signer, gameContract;

    async function initializeContract() {
        if (!window.ethereum) {
            alert("MetaMask is required to use this application.");
            return;
        }

        provider = new ethers.providers.Web3Provider(window.ethereum);
        signer = provider.getSigner();
        gameContract = new ethers.Contract(contractAddress, contractABI, signer);

        gameContract.on("GameCorrect", handleGameCorrect);
        gameContract.on("GuessIncorrect", handleGuessIncorrect);
        gameContract.on("DuplicateGuess", handleDuplicateGuess);
        gameContract.on("RevealTarget", handleRevealTarget);
    }

    function logEvent(message) {
        const eventLog = document.getElementById("eventLog");
        const listItem = document.createElement("li");
        listItem.textContent = message;
        eventLog.appendChild(listItem);
    }

    async function handleGameCorrect(player, number, suit, reward) {
        logEvent(`🎉 Correct guess by ${player}. Card: ${number}-${suit}. Reward: ${ethers.utils.formatEther(reward)} ETH`);
    }

    async function handleGuessIncorrect(player, number, suit, remainingReward) {
        logEvent(`❌ Incorrect guess by ${player}. Card: ${number}-${suit}. Remaining reward: ${ethers.utils.formatEther(remainingReward)} ETH`);
    }

    async function handleDuplicateGuess(player, number, suit) {
        logEvent(`⚠️ Duplicate guess by ${player}. Card: ${number}-${suit}`);
    }

    async function handleRevealTarget(number, suit) {
        logEvent(`🔍 Target card revealed: Number ${number}, Suit ${suit}`);
    }

    document.getElementById("guessButton").addEventListener("click", async () => {
        const number = parseInt(document.getElementById("number").value);
        const suit = parseInt(document.getElementById("suit").value);

        if (!number || !suit || number < 1 || number > 13 || suit < 1 || suit > 4) {
            alert("Please enter valid values for number (1-13) and suit (1-4).");
            return;
        }

        try {
            const tx = await gameContract.guess(number, suit, { value: ethers.utils.parseEther("1") });
            logEvent(`⏳ Guess submitted. Transaction hash: ${tx.hash}`);
            await tx.wait();
            logEvent(`✅ Transaction confirmed.`);
        } catch (error) {
            logEvent(`⚠️ Error: ${error.message}`);
        }
    });

    document.getElementById("revealButton").addEventListener("click", async () => {
        try {
            await gameContract.revealTarget();
            logEvent(`🔍 Target card reveal requested.`);
        } catch (error) {
            logEvent(`⚠️ Error: ${error.message}`);
        }
    });

    initializeContract();
});
```