418 lines
18 KiB
HTML
418 lines
18 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="color-scheme" content="light dark">
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
|
>
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css"
|
|
>
|
|
<style>
|
|
[data-theme="dark"] {
|
|
--pico-background-color: #282a36; /* Custom color */
|
|
}
|
|
|
|
.app-header {
|
|
border-bottom: 1px solid var(--pico-muted-border-color);
|
|
box-shadow: 0 1px 0 var(--pico-muted-border-color);
|
|
}
|
|
|
|
[data-theme="light"] .app-header {
|
|
background-color: #fcfcfc;
|
|
}
|
|
|
|
[data-theme="dark"] .app-header {
|
|
background-color: rgba(9, 21, 21, 0.6);
|
|
}
|
|
|
|
[data-theme="dark"] .sidebar {
|
|
border-right: 3px solid rgba(9, 21, 21, 0.6);
|
|
}
|
|
|
|
.app-header nav {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0 1.5rem;
|
|
}
|
|
|
|
.app-header hgroup {
|
|
margin: 0;
|
|
}
|
|
|
|
.app-header #theme-icon {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
margin-left: 25px;
|
|
}
|
|
|
|
.app-root {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100vh;
|
|
}
|
|
.app-body {
|
|
display: flex;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
}
|
|
.sidebar {
|
|
width: 315px;
|
|
flex-shrink: 0;
|
|
padding: 1.5rem;
|
|
border-right: 1px solid var(--pico-muted-border-color);
|
|
overflow-y: auto;
|
|
}
|
|
.sidebar nav {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
justify-content: stretch;
|
|
gap: 0;
|
|
}
|
|
|
|
.sidebar .sidebar-button {
|
|
text-align: left;
|
|
width: 100%;
|
|
padding: 0.75rem 1rem 0.75rem 0;
|
|
border-bottom: 1px solid var(--pico-muted-border-color);
|
|
|
|
text-decoration: none !important;
|
|
}
|
|
|
|
[data-theme="dark"] hgroup>:not(:first-child):last-child {
|
|
--pico-color: revert;
|
|
}
|
|
|
|
.main-content {
|
|
flex: 1;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.main-content section.hidden {
|
|
display: none !important;
|
|
}
|
|
|
|
.main-content section p.mt-3 {
|
|
margin-top: 3rem;
|
|
}
|
|
|
|
.main-content section h3.mt-3 {
|
|
margin-top: 3rem;
|
|
}
|
|
|
|
.main-content section .theory-question:first-of-type {
|
|
margin-top: 2.5rem;
|
|
}
|
|
|
|
.main-content section .theory-question {
|
|
border-top: 1px solid var(--pico-muted-border-color);
|
|
padding-top: 1.5rem;
|
|
padding-bottom: 1rem;
|
|
margin-bottom: 0;
|
|
margin-top: 0;
|
|
}
|
|
|
|
.main-content section .theory-question p.question {
|
|
font-weight: 500;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
.main-content section .theory-question p.extra-details {
|
|
margin-bottom: 1.5rem;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.main-content section hgroup p {
|
|
padding-top: 5px;
|
|
}
|
|
|
|
/* Mobile: hide sidebar, show desktop-only message */
|
|
@media (max-width: 768px) {
|
|
.sidebar {
|
|
display: none;
|
|
}
|
|
.main-content {
|
|
display: none;
|
|
}
|
|
.mobile-message {
|
|
display: flex !important;
|
|
}
|
|
}
|
|
.mobile-message {
|
|
display: none;
|
|
flex: 1;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 2rem;
|
|
text-align: center;
|
|
}
|
|
.mobile-message article {
|
|
max-width: 28rem;
|
|
}
|
|
</style>
|
|
<title>Lab 6</title>
|
|
</head>
|
|
<body>
|
|
<div class="app-root">
|
|
<header class="app-header">
|
|
<nav>
|
|
<ul>
|
|
<li>
|
|
<hgroup>
|
|
<h2>Lab 6</h2>
|
|
<p>Distributed Systems and Blockchain</p>
|
|
</hgroup>
|
|
</li>
|
|
</ul>
|
|
<ul>
|
|
<li>
|
|
<details class="dropdown">
|
|
<summary>
|
|
Documentation
|
|
</summary>
|
|
<ul>
|
|
<li><a href="https://api.next.code-camp.org/cdn/viem-docs.pdf" target="_blank">Viem</a></li>
|
|
<li><a href="https://api.next.code-camp.org/cdn/solidity-docs.pdf" target="_blank">Solidity</a></li>
|
|
<li><a href="https://api.next.code-camp.org/cdn/crowdfunding-contract.html" target="_blank">Contract example</a></li>
|
|
<li><a href="https://api.next.code-camp.org/cdn/crowdfunding-tests.html" target="_blank">Tests example</a></li>
|
|
<li><a href="https://gitea.next.code-camp.org/dsb/next-coins" target="_blank">React example app</a></li>
|
|
<li><a href="https://gitea.next.code-camp.org/dsb/wagmi-lesson" target="_blank">Wagmi example app</a></li>
|
|
</ul>
|
|
</details>
|
|
</li>
|
|
|
|
<li>
|
|
<span id="theme-icon" aria-hidden="true" onclick="cycleTheme()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 32 32" fill="currentColor" class="icon-theme-toggle "><clipPath id="theme-toggle-cutout"><path d="M0-11h25a1 1 0 0017 13v30H0Z"></path></clipPath><g clip-path="url(#theme-toggle-cutout)"><circle cx="16" cy="16" r="8.4"></circle><path d="M18.3 3.2c0 1.3-1 2.3-2.3 2.3s-2.3-1-2.3-2.3S14.7.9 16 .9s2.3 1 2.3 2.3zm-4.6 25.6c0-1.3 1-2.3 2.3-2.3s2.3 1 2.3 2.3-1 2.3-2.3 2.3-2.3-1-2.3-2.3zm15.1-10.5c-1.3 0-2.3-1-2.3-2.3s1-2.3 2.3-2.3 2.3 1 2.3 2.3-1 2.3-2.3 2.3zM3.2 13.7c1.3 0 2.3 1 2.3 2.3s-1 2.3-2.3 2.3S.9 17.3.9 16s1-2.3 2.3-2.3zm5.8-7C9 7.9 7.9 9 6.7 9S4.4 8 4.4 6.7s1-2.3 2.3-2.3S9 5.4 9 6.7zm16.3 21c-1.3 0-2.3-1-2.3-2.3s1-2.3 2.3-2.3 2.3 1 2.3 2.3-1 2.3-2.3 2.3zm2.4-21c0 1.3-1 2.3-2.3 2.3S23 7.9 23 6.7s1-2.3 2.3-2.3 2.4 1 2.4 2.3zM6.7 23C8 23 9 24 9 25.3s-1 2.3-2.3 2.3-2.3-1-2.3-2.3 1-2.3 2.3-2.3z"></path></g></svg>
|
|
</span>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
</header>
|
|
|
|
<div class="app-body">
|
|
<aside class="sidebar">
|
|
<nav>
|
|
<a href="#theory" class="sidebar-button">Questions</a>
|
|
<a href="#contract" class="sidebar-button">Coding - Solidity</a>
|
|
<a href="#react" class="sidebar-button">Coding - Wagmi</a>
|
|
</nav>
|
|
</aside>
|
|
|
|
<main class="main-content container" id="main-content">
|
|
<section id="theory">
|
|
<hgroup>
|
|
<h2>Theory</h2>
|
|
<p>Answer the following questions to the best of your ability. Each question has exactly one correct answer. There are no negative marks for incorrect answers.</p>
|
|
</hgroup>
|
|
|
|
|
|
<div id="theory-questions"></div>
|
|
</section>
|
|
|
|
|
|
<section id="contract" class="hidden">
|
|
<h2>Coding - Solidity: Powers of Two</h2>
|
|
|
|
<p>The repository you downloaded contains a Hardhat starter project. Open the file in hardhat/contracts/PowersOfTwo.sol (that's where you will write your solution for this task). The file already has the boilerplate code written for you.</p>
|
|
|
|
<p>Your job is to write a contract for playing a game called "Powers of Two". The user should guess successive powers of two (2, 4, 8, 16, 32, etc), and in each step, if they answer correctly they get an extra 1 point, and if they answer incorrectly they lose a point (but if they already have 0 points, they can't go negative, i.e. the score will still remain 0 for that user). The contract should use a mapping, so that multiple addresses can play individually (i.e. we should keep track of each address's points and last correct guess).</p>
|
|
|
|
<p>For example, if we know the last correct guess for address X was the number 8 and that they have 3 points, then if they answer 16 we can save that number as their last correct guess and increase their points to 4. We keep those details for everyone that plays our game in the same contract.</p>
|
|
|
|
<p>To be more specific, the contract should have the following functions:</p>
|
|
<ul>
|
|
<li>points(addr) - which should return the number of points for the account with address <strong>addr</strong></li>
|
|
<li>last(addr) - which should return the last correctly guessed power of two by address <strong>addr</strong> (it should start with 1, if the user hasn't guessed anything yet)</li>
|
|
<li>answer(num) - for sending a number, which is the user's guess for the next power of two (this function should also update the points we store for the user)</li>
|
|
</ul>
|
|
|
|
|
|
<p class="mt-3">You can build the contract by going into the hardhat directory, and running the following command:</p>
|
|
<pre><code>npx hardhat build</code></pre>
|
|
|
|
<p class="mt-3">Hint: you have an example contract (see Documentation links on the top) to help you get started.</p>
|
|
</section>
|
|
|
|
|
|
<section id="react" class="hidden">
|
|
<h2>Coding - React: Powers of Two</h2>
|
|
|
|
<p>The repository you downloaded contains a React+Wagmi starter project. You can start the development server by running <strong>npm run dev</strong>. Your job is to create a simple page that shows the balance of the connected wallet account, and use a contract called "Powers of Two" which allows the account to play a simple game. The user interface should show the number of points, the last power of two provided correctly by the user, and to allow the user to submit an answer for the next power of two. A designer on our team has already created the design for the web app. Your job is to create the frontend in React.</p>
|
|
|
|
<h3 class="mt-3">The page (<a href="https://api.next.code-camp.org/cdn/next-powers-of-two/index.html" target="_blank" style="text-decoration: none;">design</a>)</h3>
|
|
<p>The page should retrieve the necessary information from the blockchain, including the wallet balance in the first card, the number of points in the second card, and the last correct number (which is shown in the last card as "X*2="). It should also enable the user to answer the question for the next power of two (the answer to the question X*2). If the wallet is not connected, the page should just redirect the user to the Status tab, so the user can connect their wallet.</p>
|
|
<p>Keep in mind that you shouldn't recreate the whole design, as the layout is already part of the starter template (this includes the header, the footer, and the sidebar). You should only add the missing component(s) from the main section (in the HTML source code, this will be available under the "main-content-container" element). You can create the page in "pages/home".</p>
|
|
|
|
<p class="mt-3">If you solved the previous task by writing a valid contract in Solidity, you can use that contract for testing. However, it's recommended that you use the contract deployed at address 0x5B495d0e92A8bC50b72D8502c5EfDaBea104E4a0 instead (since it's already verified and tested), and to use the ABI available below (which you can save in a file and use it by importing it whenever you need to access the contract functionality).</p>
|
|
|
|
<p>The contract has the following functions:</p>
|
|
<ul>
|
|
<li>points(addr) - which returns the number of points for the account with address <strong>addr</strong></li>
|
|
<li>last(addr) - which returns the last correctly guessed power of two by address <strong>addr</strong> (and starts with 1)</li>
|
|
<li>answer(num) - for sending a number, which is the guess for the next power of two</li>
|
|
</ul>
|
|
|
|
<p class="mt-3">Important links:</p>
|
|
<ul>
|
|
<li><a href="https://api.next.code-camp.org/cdn/next-powers-of-two/index.html" target="_blank">The page design</a></li>
|
|
<li><a href="https://api.next.code-camp.org/cdn/next-powers-of-two/abi.html" target="_blank">The ABI</a></li>
|
|
</ul>
|
|
</section>
|
|
</main>
|
|
|
|
<div class="mobile-message">
|
|
<article>
|
|
<header>
|
|
<strong>Desktop required</strong>
|
|
</header>
|
|
<p>This exam layout needs a larger screen. Please open it on a desktop computer.</p>
|
|
</article>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
function setupTheme() {
|
|
var THEME_KEY = 'dsb-exam-theme';
|
|
var html = document.documentElement;
|
|
var icon = document.getElementById('theme-icon');
|
|
|
|
function applyTheme(theme) {
|
|
html.setAttribute('data-theme', theme);
|
|
try { localStorage.setItem(THEME_KEY, theme); } catch (e) {}
|
|
}
|
|
|
|
function cycleTheme(ev) {
|
|
ev.preventDefault();
|
|
|
|
var next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
|
applyTheme(next);
|
|
}
|
|
|
|
try {
|
|
var saved = localStorage.getItem(THEME_KEY);
|
|
if (saved === 'light' || saved === 'dark') applyTheme(saved);
|
|
} catch (e) {}
|
|
|
|
if (icon) icon.addEventListener('click', cycleTheme);
|
|
}
|
|
|
|
function setupSidebar() {
|
|
var sidebar = document.querySelector('.sidebar');
|
|
var sidebarButtons = sidebar.querySelectorAll('.sidebar-button');
|
|
sidebarButtons.forEach(function(button) {
|
|
button.addEventListener('click', function() {
|
|
var target = button.getAttribute('href');
|
|
var targetSection = document.querySelector(target);
|
|
|
|
var sections = document.querySelectorAll('.main-content section');
|
|
|
|
if (targetSection) {
|
|
document.body.style.opacity = 0;
|
|
sections.forEach(function(section) {
|
|
section.classList.add('hidden');
|
|
});
|
|
|
|
targetSection.classList.remove('hidden');
|
|
setTimeout(function() {
|
|
window.scrollTo(0, 0);
|
|
document.body.style.opacity = 1;
|
|
}, 50);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function setupTheory() {
|
|
|
|
function setupTheoryCheckboxes() {
|
|
var theoryCheckboxes = document.querySelectorAll('.theory-question input[type="checkbox"]');
|
|
theoryCheckboxes.forEach(function(checkbox) {
|
|
checkbox.addEventListener('change', function() {
|
|
var question = checkbox.closest('.theory-question');
|
|
var questionId = question.getAttribute('id').split('-')[1];
|
|
var answer = checkbox.getAttribute('name').split('-')[2];
|
|
var otherCheckboxes = question.querySelectorAll('input[type="checkbox"]');
|
|
otherCheckboxes.forEach(function(otherCheckbox) {
|
|
const otherAnswer = otherCheckbox.getAttribute('name').split('-')[2];
|
|
if (otherAnswer !== answer) {
|
|
otherCheckbox.checked = false;
|
|
}
|
|
});
|
|
|
|
checkbox.checked = true;
|
|
fetch('/theory-answer', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
questionId: Number(questionId),
|
|
answer: answer
|
|
}),
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(function(response) {
|
|
if (!response.ok) {
|
|
alert('Failed to submit answer.');
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
alert('Failed to submit answer.');
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
fetch('/theory')
|
|
.then(response => response.json())
|
|
.then(function(data) {
|
|
var theory = document.querySelector('#theory');
|
|
|
|
var html = '';
|
|
for (var i = 0; i < data.length; i++) {
|
|
var questionId = data[i].id;
|
|
var question = data[i].question;
|
|
var details = data[i].details;
|
|
var options = data[i].options;
|
|
var answer = data[i].answer;
|
|
|
|
html += '<div class="theory-question" id="question-' + questionId + '">';
|
|
html += '<p class="question">' + (i + 1) + '. ' + question + "</p>";
|
|
if (details) {
|
|
html += '<p class="extra-details">' + details + "</p>";
|
|
}
|
|
const sortedOptionKeys = Object.keys(options).sort();
|
|
|
|
html += '<fieldset>';
|
|
sortedOptionKeys.forEach(function(optionKey) {
|
|
html += '<label>';
|
|
html += '<input type="checkbox" name="question-' + questionId + '-' + optionKey + '"' + (answer === optionKey ? ' checked' : '') +'>';
|
|
html += ' ' + options[optionKey];
|
|
html += '</label>';
|
|
});
|
|
html += '</fieldset>';
|
|
html += '</div>';
|
|
}
|
|
|
|
document.querySelector('#theory-questions').innerHTML = html;
|
|
setupTheoryCheckboxes();
|
|
});
|
|
}
|
|
|
|
setupTheme();
|
|
setupSidebar();
|
|
setupTheory();
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|