Implement choice specification

This commit is contained in:
2021-04-04 22:45:15 -04:00
parent f28ac11427
commit 78edde8ca8
4 changed files with 186 additions and 132 deletions

View File

@@ -2,43 +2,29 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" href="%PUBLIC_URL%/pure-min.css" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta
name="description" name="description"
content="Web site created using create-react-app" content="Web site helping you to make a decision"
/> />
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.5/build/pure-min.css" integrity="sha384-G9DpmGxRIF6tpgbrkZVcZDeIomEU22LgTguPAI739bbKytjPE/kHTK5YxjJAAEXC" crossorigin="anonymous">
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!-- <title>Make a choice</title>
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file. <div id="main">
The build step will place the bundled scripts into the <body> tag. <div class="header">
<h1>Make a choice</h1>
</div>
To begin the development, run `npm start` or `yarn start`. <div class="content">
To create a production bundle, use `npm run build` or `yarn build`. <div id="root"></div>
--> </div>
</div>
</body> </body>
</html> </html>

11
public/pure-min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +1,41 @@
body { body {
font: 14px "Century Gothic", Futura, sans-serif; color: #777;
margin: 20px;
} }
ol, ul { /*
padding-left: 30px; The content `<div>` is where all your content goes.
*/
.content {
margin: 0 auto;
padding: 0 2em;
max-width: 800px;
margin-bottom: 50px;
line-height: 1.6em;
}
.header {
margin: 0;
color: #333;
text-align: center;
padding: 2.5em 2em 0;
border-bottom: 1px solid #eee;
}
.header h1 {
margin: 0.2em 0;
font-size: 3em;
font-weight: 300;
}
.header h2 {
font-weight: 300;
color: #ccc;
padding: 0;
margin-top: 0;
}
.content-subhead {
margin: 50px 0 20px 0;
font-weight: 300;
color: #888;
} }
.board-row:after { .board-row:after {
@@ -13,8 +44,8 @@ ol, ul {
display: table; display: table;
} }
.status { .space-1 {
margin-bottom: 10px; margin-top: 1em;
} }
.square { .square {

View File

@@ -2,137 +2,163 @@ import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import "./index.css"; import "./index.css";
function Square(props) { function shuffleArray(array) {
return ( /* https://stackoverflow.com/a/12646864 */
<button className="square" onClick={props.onClick}> for (let i = array.length - 1; i > 0; i--) {
{props.value} const j = Math.floor(Math.random() * (i + 1));
</button> [array[i], array[j]] = [array[j], array[i]];
);
}
class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
);
}
render() {
return (
<div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
} }
} }
class Game extends React.Component { class Decision {
constructor(choiceA, choiceB) {
this.choiceA = choiceA;
this.choiceB = choiceB;
this.result = null;
}
static createDecisions(choices) {
let decisions = [];
for (var i = 0; i < choices.length; i++) {
for (var j = 0; j < choices.length; j++) {
if (i !== j) {
let d = new Decision(choices[i], choices[j]);
decisions.push(d);
}
}
}
shuffleArray(decisions);
return decisions;
}
}
class Options extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
history: [{ squares: Array(9).fill(null) }], choicesText: null,
stepNumber: 0, choices: [],
xIsNext: true, decisions: null,
warnings: null,
}; };
} }
jumpTo(step) { startChoosing() {
this.setState({ this.setState({
stepNumber: step, decisions: Decision.createDecisions(this.state.choices),
xIsNext: step % 2 === 0,
}); });
} }
handleClick(i) { choicesTextToChoices(choicesText) {
const history = this.state.history.slice(0, this.state.stepNumber + 1); let choices = choicesText.split("\n");
const current = history[history.length - 1]; choices = choices.filter((choice) => choice.length > 0);
const squares = current.squares.slice(); return choices;
if (calculateWinner(squares) || squares[i]) { }
return;
} choicesOnChange(event) {
squares[i] = this.state.xIsNext ? "X" : "O"; let choicesText = event.target.value;
let choices = this.choicesTextToChoices(choicesText);
this.setState({ this.setState({
history: history.concat([{ squares: squares }]), choicesText: choicesText,
stepNumber: history.length, choices: choices,
xIsNext: !this.state.xIsNext,
}); });
} }
render() { renderChoosing() {
const history = this.state.history; const decisions = this.state.decisions;
const current = history[this.state.stepNumber]; const decisionsToRender = decisions.map((decision, index) => {
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ? "Go to move #" + move : "Go to game start";
return ( return (
<li key={move}> <li key={index}>
<button onClick={() => this.jumpTo(move)}>{desc}</button> {decision.choiceA} - {decision.choiceB}
</li> </li>
); );
}); });
let status;
if (winner) {
status = "Winner " + winner;
} else {
status = "Next player: " + (this.state.xIsNext ? "X" : "O");
}
return ( return (
<div className="game"> <div className="pure-g">
<div className="game-board"> <div className="pure-u-1">
<Board <ul>{decisionsToRender}</ul>
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div> </div>
</div> </div>
); );
} }
}
// ======================================== renderButton() {
let choices = this.state.choices;
let button = null;
if (choices.length > 1 && choices[1]) {
button = (
<button
className="pure-button pure-button-primary space-1"
onClick={() => this.startChoosing()}
>
Start choosing
</button>
);
} else {
button = (
<button className="pure-button pure-button-disabled space-1">
Start choosing
</button>
);
}
return button;
}
ReactDOM.render(<Game />, document.getElementById("root")); renderChoiceInputField() {
return (
<div className="pure-u-1">
<h2 className="content-subhead">Type or paste your choices</h2>
<form className="pure-form" onSubmit={() => this.onSubmit}>
<fieldset className="pure-group">
<textarea
onChange={(event) => this.choicesOnChange(event)}
className="pure-input-1"
placeholder="Enter one choice per line"
></textarea>
</fieldset>
</form>
</div>
);
}
function calculateWinner(squares) { renderCurrentChoices() {
const lines = [ let choices = this.state.choices;
[0, 1, 2], let choicesToRender = choices.map((choice, index) => {
[3, 4, 5], return <li key={index}>{choice}</li>;
[6, 7, 8], });
[0, 3, 6],
[1, 4, 7], let button = this.renderButton();
[2, 5, 8], return (
[0, 4, 8], <div className="pure-u-1">
[2, 4, 6], <h2 className="content-subhead">Your choices</h2>
]; <div className="pure-menu">
for (let i = 0; i < lines.length; i++) { <ul>{choicesToRender}</ul>
const [a, b, c] = lines[i]; </div>
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { <div>{button}</div>
return squares[a]; </div>
);
}
renderSpecifyChoices() {
let input = this.renderChoiceInputField();
let output = this.renderCurrentChoices();
return (
<div className="pure-g">
{input}
{output}
</div>
);
}
render() {
if (!this.state.decisions) {
return this.renderSpecifyChoices();
} else {
return this.renderChoosing();
} }
} }
return null;
} }
ReactDOM.render(<Options />, document.getElementById("root"));