Last active
October 3, 2022 13:10
-
-
Save TheCyberWatchers/72c28c53a9e4fce1edf185944324850c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
---------------- index.css ----------------- | |
body { | |
margin: 0; | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', | |
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', | |
sans-serif; | |
-webkit-font-smoothing: antialiased; | |
-moz-osx-font-smoothing: grayscale; | |
} | |
code { | |
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', | |
monospace; | |
} | |
.App { | |
text-align: center; | |
} | |
.App-header { | |
background-color: #282c34; | |
min-height: 100vh; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
font-size: calc(10px + 2vmin); | |
color: white; | |
} | |
.App-link { | |
color: #61dafb; | |
} | |
input[type=number] { | |
font-size: calc(10px + 2vmin); | |
width: 4em; | |
} | |
button { | |
font-size: calc(10px + 2vmin); | |
} | |
textarea { | |
font-size: calc(10px + 2vmin); | |
} | |
.ContractInfo { | |
font-size: calc(10px + 1vmin); | |
text-align: left; | |
width: 90vw; | |
height: 7em; | |
padding: 2vw; | |
overflow-x: scroll; | |
white-space: pre; | |
} | |
---------------- index.js ----------------- | |
import React from 'react'; | |
import AppViews from './views/AppViews'; | |
import DeployerViews from './views/DeployerViews'; | |
import AttacherViews from './views/AttacherViews'; | |
import {renderDOM, renderView} from './views/render'; | |
import './index.css'; | |
import * as backend from './build/index.main.mjs'; | |
import { loadStdlib } from '@reach-sh/stdlib'; | |
const reach = loadStdlib(process.env); | |
import { ALGO_MyAlgoConnect as MyAlgoConnect } | |
from '@reach-sh/stdlib'; | |
reach.setWalletFallback(reach.walletFallback({ | |
providerEnv: 'TestNet', MyAlgoConnect })); | |
const handToInt = {'ROCK': 0, 'PAPER': 1, 'SCISSORS': 2}; | |
const intToOutcome = ['Bob wins!', 'Draw!', 'Alice wins!']; | |
const {standardUnit} = reach; | |
const defaults = {defaultFundAmt: '10', defaultWager: '3', standardUnit}; | |
class App extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = {view: 'ConnectAccount', ...defaults}; | |
} | |
async componentDidMount() { | |
const acc = await reach.getDefaultAccount(); | |
const balAtomic = await reach.balanceOf(acc); | |
const bal = reach.formatCurrency(balAtomic, 4); | |
this.setState({acc, bal}); | |
if (await reach.canFundFromFaucet()) { | |
this.setState({view: 'FundAccount'}); | |
} else { | |
this.setState({view: 'DeployerOrAttacher'}); | |
} | |
} | |
async fundAccount(fundAmount) { | |
await reach.fundFromFaucet(this.state.acc, reach.parseCurrency(fundAmount)); | |
this.setState({view: 'DeployerOrAttacher'}); | |
} | |
async skipFundAccount() { this.setState({view: 'DeployerOrAttacher'}); } | |
selectAttacher() { this.setState({view: 'Wrapper', ContentView: Attacher}); } | |
selectDeployer() { this.setState({view: 'Wrapper', ContentView: Deployer}); } | |
render() { return renderView(this, AppViews); } | |
} | |
class Player extends React.Component { | |
random() { return reach.hasRandom.random(); } | |
async getHand() { // Fun([], UInt) | |
const hand = await new Promise(resolveHandP => { | |
this.setState({view: 'GetHand', playable: true, resolveHandP}); | |
}); | |
this.setState({view: 'WaitingForResults', hand}); | |
return handToInt[hand]; | |
} | |
seeOutcome(i) { this.setState({view: 'Done', outcome: intToOutcome[i]}); } | |
informTimeout() { this.setState({view: 'Timeout'}); } | |
playHand(hand) { this.state.resolveHandP(hand); } | |
} | |
class Deployer extends Player { | |
constructor(props) { | |
super(props); | |
this.state = {view: 'SetWager'}; | |
} | |
setWager(wager) { this.setState({view: 'Deploy', wager}); } | |
async deploy() { | |
const ctc = this.props.acc.contract(backend); | |
this.setState({view: 'Deploying', ctc}); | |
this.wager = reach.parseCurrency(this.state.wager); // UInt | |
this.deadline = {ETH: 10, ALGO: 100, CFX: 1000}[reach.connector]; // UInt | |
backend.Alice(ctc, this); | |
const ctcInfoStr = JSON.stringify(await ctc.getInfo(), null, 2); | |
this.setState({view: 'WaitingForAttacher', ctcInfoStr}); | |
} | |
render() { return renderView(this, DeployerViews); } | |
} | |
class Attacher extends Player { | |
constructor(props) { | |
super(props); | |
this.state = {view: 'Attach'}; | |
} | |
attach(ctcInfoStr) { | |
const ctc = this.props.acc.contract(backend, JSON.parse(ctcInfoStr)); | |
this.setState({view: 'Attaching'}); | |
backend.Bob(ctc, this); | |
} | |
async acceptWager(wagerAtomic) { // Fun([UInt], Null) | |
const wager = reach.formatCurrency(wagerAtomic, 4); | |
return await new Promise(resolveAcceptedP => { | |
this.setState({view: 'AcceptTerms', wager, resolveAcceptedP}); | |
}); | |
} | |
termsAccepted() { | |
this.state.resolveAcceptedP(); | |
this.setState({view: 'WaitingForTurn'}); | |
} | |
render() { return renderView(this, AttacherViews); } | |
} | |
renderDOM(<App />); | |
---------------- index.rsh ----------------- | |
'reach 0.1'; | |
const [ isHand, ROCK, PAPER, SCISSORS ] = makeEnum(3); | |
const [ isOutcome, B_WINS, DRAW, A_WINS ] = makeEnum(3); | |
const winner = (handAlice, handBob) => | |
((handAlice + (4 - handBob)) % 3); | |
assert(winner(ROCK, PAPER) == B_WINS); | |
assert(winner(PAPER, ROCK) == A_WINS); | |
assert(winner(ROCK, ROCK) == DRAW); | |
forall(UInt, handAlice => | |
forall(UInt, handBob => | |
assert(isOutcome(winner(handAlice, handBob))))); | |
forall(UInt, (hand) => | |
assert(winner(hand, hand) == DRAW)); | |
const Player = { | |
...hasRandom, | |
getHand: Fun([], UInt), | |
seeOutcome: Fun([UInt], Null), | |
informTimeout: Fun([], Null), | |
}; | |
export const main = Reach.App(() => { | |
const Alice = Participant('Alice', { | |
...Player, | |
wager: UInt, // atomic units of currency | |
deadline: UInt, // time delta (blocks/rounds) | |
}); | |
const Bob = Participant('Bob', { | |
...Player, | |
acceptWager: Fun([UInt], Null), | |
}); | |
init(); | |
const informTimeout = () => { | |
each([Alice, Bob], () => { | |
interact.informTimeout(); | |
}); | |
}; | |
Alice.only(() => { | |
const wager = declassify(interact.wager); | |
const deadline = declassify(interact.deadline); | |
}); | |
Alice.publish(wager, deadline) | |
.pay(wager); | |
commit(); | |
Bob.only(() => { | |
interact.acceptWager(wager); | |
}); | |
Bob.pay(wager) | |
.timeout(relativeTime(deadline), () => closeTo(Alice, informTimeout)); | |
var outcome = DRAW; | |
invariant( balance() == 2 * wager && isOutcome(outcome) ); | |
while ( outcome == DRAW ) { | |
commit(); | |
Alice.only(() => { | |
const _handAlice = interact.getHand(); | |
const [_commitAlice, _saltAlice] = makeCommitment(interact, _handAlice); | |
const commitAlice = declassify(_commitAlice); | |
}); | |
Alice.publish(commitAlice) | |
.timeout(relativeTime(deadline), () => closeTo(Bob, informTimeout)); | |
commit(); | |
unknowable(Bob, Alice(_handAlice, _saltAlice)); | |
Bob.only(() => { | |
const handBob = declassify(interact.getHand()); | |
}); | |
Bob.publish(handBob) | |
.timeout(relativeTime(deadline), () => closeTo(Alice, informTimeout)); | |
commit(); | |
Alice.only(() => { | |
const saltAlice = declassify(_saltAlice); | |
const handAlice = declassify(_handAlice); | |
}); | |
Alice.publish(saltAlice, handAlice) | |
.timeout(relativeTime(deadline), () => closeTo(Bob, informTimeout)); | |
checkCommitment(commitAlice, saltAlice, handAlice); | |
outcome = winner(handAlice, handBob); | |
continue; | |
} | |
assert(outcome == A_WINS || outcome == B_WINS); | |
transfer(2 * wager).to(outcome == A_WINS ? Alice : Bob); | |
commit(); | |
each([Alice, Bob], () => { | |
interact.seeOutcome(outcome); | |
}); | |
}); | |
/*/*/*/*/*/*/*/*/* VIEWS *\*\*\*\*\*\*\*\*\*\ | |
---------------- AppViews.js ---------------------- | |
import React from 'react'; | |
const exports = {}; | |
exports.Wrapper = class extends React.Component { | |
render() { | |
const {content} = this.props; | |
return ( | |
<div className="App"> | |
<header className="App-header" id="root"> | |
<h1>Rock, Paper, Scissors</h1> | |
{content} | |
</header> | |
</div> | |
); | |
} | |
} | |
exports.ConnectAccount = class extends React.Component { | |
render() { | |
return ( | |
<div> | |
Please wait while we connect to your account. | |
If this takes more than a few seconds, there may be something wrong. | |
</div> | |
) | |
} | |
} | |
exports.FundAccount = class extends React.Component { | |
render() { | |
const {bal, standardUnit, defaultFundAmt, parent} = this.props; | |
const amt = (this.state || {}).amt || defaultFundAmt; | |
return ( | |
<div> | |
<h2>Fund account</h2> | |
<br /> | |
Balance: {bal} {standardUnit} | |
<hr /> | |
Would you like to fund your account with additional {standardUnit}? | |
<br /> | |
(This only works on certain devnets) | |
<br /> | |
<input | |
type='number' | |
placeholder={defaultFundAmt} | |
onChange={(e) => this.setState({amt: e.currentTarget.value})} | |
/> | |
<button onClick={() => parent.fundAccount(amt)}>Fund Account</button> | |
<button onClick={() => parent.skipFundAccount()}>Skip</button> | |
</div> | |
); | |
} | |
} | |
exports.DeployerOrAttacher = class extends React.Component { | |
render() { | |
const {parent} = this.props; | |
return ( | |
<div> | |
Please select a role: | |
<br /> | |
<p> | |
<button | |
onClick={() => parent.selectDeployer()} | |
>Deployer</button> | |
<br /> Set the wager, deploy the contract. | |
</p> | |
<p> | |
<button | |
onClick={() => parent.selectAttacher()} | |
>Attacher</button> | |
<br /> Attach to the Deployer's contract. | |
</p> | |
</div> | |
); | |
} | |
} | |
export default exports; | |
---------------- AttacherViews.js ----------------- | |
import React from 'react'; | |
import PlayerViews from './PlayerViews'; | |
const exports = {...PlayerViews}; | |
exports.Wrapper = class extends React.Component { | |
render() { | |
const {content} = this.props; | |
return ( | |
<div className="Attacher"> | |
<h2>Attacher (Bob)</h2> | |
{content} | |
</div> | |
); | |
} | |
} | |
exports.Attach = class extends React.Component { | |
render() { | |
const {parent} = this.props; | |
const {ctcInfoStr} = this.state || {}; | |
return ( | |
<div> | |
Please paste the contract info to attach to: | |
<br /> | |
<textarea spellCheck="false" | |
className='ContractInfo' | |
onChange={(e) => this.setState({ctcInfoStr: e.currentTarget.value})} | |
placeholder='{}' | |
/> | |
<br /> | |
<button | |
disabled={!ctcInfoStr} | |
onClick={() => parent.attach(ctcInfoStr)} | |
>Attach</button> | |
</div> | |
); | |
} | |
} | |
exports.Attaching = class extends React.Component { | |
render() { | |
return ( | |
<div> | |
Attaching, please wait... | |
</div> | |
); | |
} | |
} | |
exports.AcceptTerms = class extends React.Component { | |
render() { | |
const {wager, standardUnit, parent} = this.props; | |
const {disabled} = this.state || {}; | |
return ( | |
<div> | |
The terms of the game are: | |
<br /> Wager: {wager} {standardUnit} | |
<br /> | |
<button | |
disabled={disabled} | |
onClick={() => { | |
this.setState({disabled: true}); | |
parent.termsAccepted(); | |
}} | |
>Accept terms and pay wager</button> | |
</div> | |
); | |
} | |
} | |
exports.WaitingForTurn = class extends React.Component { | |
render() { | |
return ( | |
<div> | |
Waiting for the other player... | |
<br />Think about which move you want to play. | |
</div> | |
); | |
} | |
} | |
export default exports; | |
---------------- DeployerViews.js ----------------- | |
import React from 'react'; | |
import PlayerViews from './PlayerViews'; | |
const exports = {...PlayerViews}; | |
const sleep = (milliseconds) => new Promise(resolve => setTimeout(resolve, milliseconds)); | |
exports.Wrapper = class extends React.Component { | |
render() { | |
const {content} = this.props; | |
return ( | |
<div className="Deployer"> | |
<h2>Deployer (Alice)</h2> | |
{content} | |
</div> | |
); | |
} | |
} | |
exports.SetWager = class extends React.Component { | |
render() { | |
const {parent, defaultWager, standardUnit} = this.props; | |
const wager = (this.state || {}).wager || defaultWager; | |
return ( | |
<div> | |
<input | |
type='number' | |
placeholder={defaultWager} | |
onChange={(e) => this.setState({wager: e.currentTarget.value})} | |
/> {standardUnit} | |
<br /> | |
<button | |
onClick={() => parent.setWager(wager)} | |
>Set wager</button> | |
</div> | |
); | |
} | |
} | |
exports.Deploy = class extends React.Component { | |
render() { | |
const {parent, wager, standardUnit} = this.props; | |
return ( | |
<div> | |
Wager (pay to deploy): <strong>{wager}</strong> {standardUnit} | |
<br /> | |
<button | |
onClick={() => parent.deploy()} | |
>Deploy</button> | |
</div> | |
); | |
} | |
} | |
exports.Deploying = class extends React.Component { | |
render() { | |
return ( | |
<div>Deploying... please wait.</div> | |
); | |
} | |
} | |
exports.WaitingForAttacher = class extends React.Component { | |
async copyToClipboard(button) { | |
const {ctcInfoStr} = this.props; | |
navigator.clipboard.writeText(ctcInfoStr); | |
const origInnerHTML = button.innerHTML; | |
button.innerHTML = 'Copied!'; | |
button.disabled = true; | |
await sleep(1000); | |
button.innerHTML = origInnerHTML; | |
button.disabled = false; | |
} | |
render() { | |
const {ctcInfoStr} = this.props; | |
return ( | |
<div> | |
Waiting for Attacher to join... | |
<br /> Please give them this contract info: | |
<pre className='ContractInfo'> | |
{ctcInfoStr} | |
</pre> | |
<button | |
onClick={(e) => this.copyToClipboard(e.currentTarget)} | |
>Copy to clipboard</button> | |
</div> | |
) | |
} | |
} | |
export default exports; | |
---------------- PlayerViews.js ------------------- | |
import React from 'react'; | |
const exports = {}; | |
// Player views must be extended. | |
// It does not have its own Wrapper view. | |
exports.GetHand = class extends React.Component { | |
render() { | |
const {parent, playable, hand} = this.props; | |
return ( | |
<div> | |
{hand ? 'It was a draw! Pick again.' : ''} | |
<br /> | |
{!playable ? 'Please wait...' : ''} | |
<br /> | |
<button | |
disabled={!playable} | |
onClick={() => parent.playHand('ROCK')} | |
>Rock</button> | |
<button | |
disabled={!playable} | |
onClick={() => parent.playHand('PAPER')} | |
>Paper</button> | |
<button | |
disabled={!playable} | |
onClick={() => parent.playHand('SCISSORS')} | |
>Scissors</button> | |
</div> | |
); | |
} | |
} | |
exports.WaitingForResults = class extends React.Component { | |
render() { | |
return ( | |
<div> | |
Waiting for results... | |
</div> | |
); | |
} | |
} | |
exports.Done = class extends React.Component { | |
render() { | |
const {outcome} = this.props; | |
return ( | |
<div> | |
Thank you for playing. The outcome of this game was: | |
<br />{outcome || 'Unknown'} | |
</div> | |
); | |
} | |
} | |
exports.Timeout = class extends React.Component { | |
render() { | |
return ( | |
<div> | |
There's been a timeout. (Someone took too long.) | |
</div> | |
); | |
} | |
} | |
export default exports; | |
---------------- render.js ------------------------ | |
import ReactDOM from 'react-dom'; | |
import React from 'react'; | |
export function renderDOM(app) { | |
ReactDOM.render( | |
<React.StrictMode>{app}</React.StrictMode>, | |
document.getElementById('root') | |
); | |
} | |
export function renderView(parent, Views) { | |
parent.state = parent.state || {}; | |
const {view, ContentView} = parent.state; | |
const View = view === 'Wrapper' | |
? ContentView | |
: Views[view]; | |
const Wrapper = Views['Wrapper']; | |
const props = {...parent.props, ...parent.state, parent}; | |
const content = <View {...props} />; | |
return <Wrapper {...{content}} />; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment