diff --git a/contracts/hangman.sol b/contracts/hangman.sol index 31b6e73..ae4d5d1 100644 --- a/contracts/hangman.sol +++ b/contracts/hangman.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear -pragma solidity >=0.8.13 <0.8.20; +pragma solidity >=0.8.13 <=0.8.20; import "fhevm/lib/TFHE.sol"; @@ -8,21 +8,29 @@ contract HangmanFactory { event GameCreated(address indexed player, address gameContract); address public master; - euint32 private fourBytes; + euint32[] private fourBytes; + uint256 public currentWord; constructor (address _master) { master = _master; } - function initialize(bytes memory inSecret) public onlyMaster { - fourBytes = TFHE.asEuint32(inSecret); + function getWordsTotal() public view returns(uint256) { + return fourBytes.length; + } + + function addWord(bytes memory inSecret) public onlyMaster { + fourBytes.push(TFHE.asEuint32(inSecret)); } function CreateGame(address player) public returns (address) { - HangmanGame game = new HangmanGame(player); - game.setWord(fourBytes); - emit GameCreated(player, address(this)); - return address(this); + require(currentWord < fourBytes.length, "no words left"); + + HangmanGame game = new HangmanGame(player, currentWord); + game.setWord(fourBytes[currentWord]); + currentWord++; + emit GameCreated(player, address(game)); + return address(game); } modifier onlyMaster() { @@ -32,7 +40,7 @@ contract HangmanFactory { } contract HangmanGame { - euint8[] private encryptedCharsInv; + euint8[4] private encryptedCharsInv; bytes private decryptedWord; bool private luckyGuess; @@ -42,19 +50,22 @@ contract HangmanGame { uint8 private constant MAX_LETTERS = 4; uint8 private constant QUESTIONMARK = 63; + uint8 private constant UNDERSCORE = 95; bytes private wrongGuesses; address private factory; address public player; + uint256 public gameID; event GuessedCorrectly(string indexed letter); event GuessedIncorrectly(string indexed letter); - constructor (address _player) { + constructor (address _player, uint256 _gameID) { lives = 11; factory = msg.sender; player = _player; + gameID = _gameID; } function setWord(euint32 fourBytes) public onlyFactory { @@ -70,8 +81,9 @@ contract HangmanGame { TFHE.optReq(isLetter); - encryptedCharsInv.push(uppercaseLetter); - decryptedWord[i] = bytes1(uint8(QUESTIONMARK)); + //encryptedCharsInv.push(uppercaseLetter); + encryptedCharsInv[i] = uppercaseLetter; + decryptedWord[i] = bytes1(uint8(UNDERSCORE)); } } diff --git a/public/DkCrayonCrumble-ddll.ttf b/public/DkCrayonCrumble-ddll.ttf new file mode 100644 index 0000000..c376164 Binary files /dev/null and b/public/DkCrayonCrumble-ddll.ttf differ diff --git a/src/App.scss b/src/App.scss index d19bfdc..df57438 100644 --- a/src/App.scss +++ b/src/App.scss @@ -8,12 +8,12 @@ } body { - background-image: url("./assets/bg4_dark.jpg"); + background-image: url("./assets/bg4_dark2.jpg"); background-size: cover; } body.dark { - background-image: url("./assets/bg4_dark.jpg"); + background-image: url("./assets/bg4_dark2.jpg"); background-size: cover; } diff --git a/src/App.tsx b/src/App.tsx index a2748eb..9c01945 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,7 +11,6 @@ import {usePrivy, useWallets} from '@privy-io/react-auth'; import truncateEthAddress from 'truncate-eth-address'; import WalletIcon from './assets/wallet_icon.svg'; -import MicIcon from './assets/mic_icon.svg'; import Game from './Game.svelte'; import {Web3Provider} from '@ethersproject/providers'; @@ -19,11 +18,11 @@ import {Web3Provider} from '@ethersproject/providers'; import WebApp from '@twa-dev/sdk' import 'regenerator-runtime/runtime'; -import SpeechRecognition, { useSpeechRecognition } from 'react-speech-recognition'; -import { lineaTestnet } from '@wagmi/chains'; + +const GAME_FACTORY_ADDR = "0x746eD964A6B0ECF7Ab765Dfd831Bf4a715Ac33af"; async function fundWallet(walletAddress: string): Promise { - const response = await fetch('https://faucet.inco.network/api/get-faucet', { + const response = await fetch('https://faucet.testnet.inco.org/api/get-faucet', { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -36,10 +35,6 @@ async function fundWallet(walletAddress: string): Promise { return response.ok; } -const startListening = async () => { - await SpeechRecognition.startListening(); -} - function App() { const {ready, user, login, logout, authenticated} = usePrivy(); @@ -49,6 +44,10 @@ function App() { const [letter, setLetter] = useState(""); const [gameAddress, setGameAddress] = useState(""); + const [wordReveal, setWordReveal] = useState("____"); + const [nOfLives, setNOfLives] = useState(11); + const [hasWon, setHasWon] = useState(false); + const [wrongGuesses, setWrongGuesses] = useState>([]); // eslint-disable-next-line @typescript-eslint/no-explicit-any const handleLetterInput = (e: any) => { @@ -56,7 +55,13 @@ function App() { if (e.target.value.length == 1 || e.target.value.length == 4) { setLetter(e.target.value); } -}; + }; + + const handleOnKeyDown = (e: React.KeyboardEvent) => { + if (e.keyCode === 8) { + setLetter(""); + } + }; const { wallets } = useWallets(); const w0 = wallets[0]; @@ -82,7 +87,7 @@ function App() { w0?.getEthersProvider().then(async (provider) => { const balance = await getBalance(provider); - if (balance?.lte(100000000000000)) { + if (!isFunded && balance?.lte(100000000000000)) { const funded = await fundWallet(w0.address); if (funded) { setIsFunded(true); @@ -123,14 +128,14 @@ function App() { await provider?.send("wallet_addEthereumChain", [ { chainId: "0x2382", //9090 - chainName: "Inco Network", + chainName: "Inco Gentry", nativeCurrency: { name: "IncoToken", symbol: "INCO", decimals: 18, }, - rpcUrls: ["https://evm-rpc.inco.network/"], - blockExplorerUrls: ["https://explorer.inco.network/"], + rpcUrls: ["https://testnet.inco.org/"], + blockExplorerUrls: ["https://explorer.inco.org/"], }, ]); } @@ -140,18 +145,39 @@ function App() { const gameAddr = await createNewGame(() => setIsInGame(true)); setGameAddress(gameAddr); setIsInGame(true); + + //Reset state + setLetter(""); + setWordReveal("____"); + setNOfLives(11); + setHasWon(false); + setWrongGuesses([]); console.info("Game created"); console.info(gameAddr); - - const res = await showWord(gameAddr); } const guess = async () => { console.info("Making a guess"); - const res = await guessLetter(letter, () => {}); - console.info("Guess made"); - console.info(res); + const correctGuess = await guessLetter(letter, () => { + setLetter(""); + }); + console.info("Guessed correctly?:"); + console.info(correctGuess); + if (correctGuess) { + const revealedWord = await showWord(); + console.info("Word:"); + console.info(revealedWord); + setWordReveal(revealedWord); + //Search revealed word for underscores + if (revealedWord.indexOf("_") === -1) { + setHasWon(true); + } + } else { + console.info("Updating wrong guess list:") + setWrongGuesses(wrongGuesses.concat([letter])); + console.info(wrongGuesses); + } } @@ -164,10 +190,9 @@ function App() { } const signer = await provider?.getSigner(); - //signed address const address = await signer?.getAddress(); - const factoryContract = new Contract('0x53403B0Bc452A5Fb8A890479077360611D623544', factoryABI, signer); + const factoryContract = new Contract(GAME_FACTORY_ADDR, factoryABI, signer); const res = await factoryContract.CreateGame(address); hook(); @@ -176,13 +201,14 @@ function App() { // eslint-disable-next-line @typescript-eslint/no-explicit-any const event = receipt.events.find((event: any) => event.event === 'GameCreated'); - const [playerAddr, gameAddr] = event.args; + const [playerAddr, gameAddr] = event.args; + console.info("playerAddr:", playerAddr); return gameAddr; } // eslint-disable-next-line @typescript-eslint/no-explicit-any - const guessLetter = async ( letter: string, hook : () => any ): Promise => { + const guessLetter = async ( letter: string, hook : () => any ): Promise => { const provider = await w0?.getEthersProvider(); const network = await provider.getNetwork(); if (network.chainId != 9090) { @@ -190,41 +216,56 @@ function App() { } const signer = await provider?.getSigner(); - //signed address - const address = await signer?.getAddress(); const gameContract = new Contract(gameAddress, gameABI, signer); console.info("Before estimate gas") //const gasEstimated = await gameContract.estimateGas.guessLetter(letter); - const res = await gameContract.guessLetter(letter, { - gasLimit: 30000000 - }); + const res = await gameContract.guessLetter(letter/*, { + gasLimit: 300000000 + }*/); hook(); const receipt = await res.wait(); + /* // eslint-disable-next-line @typescript-eslint/no-explicit-any const event = receipt.events.find((event: any) => event.event === 'GuessedCorrectly' || event.event === 'GuessedIncorrectly'); if (event) { + console.info("Guessed event:"); + console.info(event); const [retLetter] = event.args; return retLetter; } - return letter; + */ + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const guessedCorrectlyEvent = receipt.events.find((event: any) => event.event === 'GuessedCorrectly'); + if (guessedCorrectlyEvent) { + console.info("Guessed correctly!!"); + const [correctLetter] = guessedCorrectlyEvent.args; + console.info("correctLetter:"); + console.info(correctLetter); + return true; + } + else { + if (nOfLives > 0) { + setNOfLives(nOfLives - 1); + } + return false; + } } // eslint-disable-next-line @typescript-eslint/no-explicit-any - const showWord = async (gameAddress: string): Promise => { + const showWord = async (): Promise => { const provider = await w0?.getEthersProvider(); const network = await provider.getNetwork(); if (network.chainId != 9090) { addNetwork(); } - const signer = await provider?.getSigner(); - const gameContract = new Contract(gameAddress, gameABI, provider); const res = await gameContract.showWord(); @@ -241,9 +282,14 @@ function App() { new Game({ target: svelteRef.current as Element, - props: {} + props: { + wrongGuesses: [...wrongGuesses], + lives: nOfLives, + currentWord: wordReveal, + hasWon: hasWon + } }) - }, []) + }, [nOfLives, wordReveal, wrongGuesses, hasWon]) return ( <> @@ -279,24 +325,18 @@ function App() { : -
{/*
- {/* - {"ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map((letter, index) => { - return

{letter}

; - })} -
*/ - - isInGame ? +
{ + isInGame && nOfLives > 0 && !hasWon ?
- +
: }
diff --git a/src/Game.svelte b/src/Game.svelte index 6ce3b6e..daabeca 100644 --- a/src/Game.svelte +++ b/src/Game.svelte @@ -1,11 +1,126 @@ -
- +
+
+ {#each wrongWords as letter} + {letter} + {/each} +
+ item +
+ +
+
+ {#each splitWord as letter} +
+

{letter}

+
+ {/each} +
+
+ {#if hasWon} +
+ +
+ {/if}
- \ No newline at end of file diff --git a/src/Hangman.svelte b/src/Hangman.svelte deleted file mode 100644 index 1a3a22c..0000000 --- a/src/Hangman.svelte +++ /dev/null @@ -1,104 +0,0 @@ - - -
-
-
Wrong guesses:
-
- {#each wrongGuesses as letter} - {letter} - {/each} -
- item -
- -
-
-
-

A

-
-
-

?

-
-
-

?

-
-
-

?

-
-
-
- -
- - \ No newline at end of file diff --git a/src/assets/bg4_dark2.jpg b/src/assets/bg4_dark2.jpg new file mode 100644 index 0000000..26c67dc Binary files /dev/null and b/src/assets/bg4_dark2.jpg differ diff --git a/src/assets/factory_abi.json b/src/assets/factory_abi.json index d1f2a4c..e9ddea6 100644 --- a/src/assets/factory_abi.json +++ b/src/assets/factory_abi.json @@ -56,11 +56,37 @@ "type": "bytes" } ], - "name": "initialize", + "name": "addWord", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "currentWord", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getWordsTotal", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "master", diff --git a/src/assets/game_abi.json b/src/assets/game_abi.json index 256dbac..174d87a 100644 --- a/src/assets/game_abi.json +++ b/src/assets/game_abi.json @@ -5,6 +5,11 @@ "internalType": "address", "name": "_player", "type": "address" + }, + { + "internalType": "uint256", + "name": "_gameID", + "type": "uint256" } ], "stateMutability": "nonpayable", @@ -36,6 +41,19 @@ "name": "GuessedIncorrectly", "type": "event" }, + { + "inputs": [], + "name": "gameID", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/src/index.css b/src/index.css index 9365cbc..9666364 100644 --- a/src/index.css +++ b/src/index.css @@ -63,7 +63,7 @@ button { @font-face { font-family: 'SketchScript'; - src: url('SketchScript-Regular.otf') format('opentype'); + src: url('DkCrayonCrumble-ddll.ttf'); font-weight: normal; font-style: normal; } diff --git a/src/main.tsx b/src/main.tsx index 48b0b33..e4e8a7d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -19,16 +19,16 @@ const incoChain: Chain = { }, rpcUrls: { default: { - http: ["https://evm-rpc.inco.network/"], + http: ["https://testnet.inco.org/"], }, public: { - http: ["https://evm-rpc.inco.network/"], + http: ["https://testnet.inco.org/"], }, }, blockExplorers: { default: { name: "Explorer", - url: "https://explorer.inco.network/", + url: "https://explorer.inco.org/", }, }, }; diff --git a/vite.config.ts b/vite.config.ts index 8c6600a..2fbe0c7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -16,5 +16,5 @@ export default defineConfig({ define: { 'process.env': {} }, - base: '/' + base: '/IncoHangman/' })