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="main">
<div class="header">
<h1>Make a choice</h1>
</div>
<div class="content">
<div id="root"></div> <div id="root"></div>
<!-- </div>
This HTML file is a template. </div>
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.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</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;
} }
squares[i] = this.state.xIsNext ? "X" : "O";
choicesOnChange(event) {
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;
} }
// ======================================== 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>
);
}
ReactDOM.render(<Game />, document.getElementById("root")); renderCurrentChoices() {
let choices = this.state.choices;
let choicesToRender = choices.map((choice, index) => {
return <li key={index}>{choice}</li>;
});
function calculateWinner(squares) { let button = this.renderButton();
const lines = [ return (
[0, 1, 2], <div className="pure-u-1">
[3, 4, 5], <h2 className="content-subhead">Your choices</h2>
[6, 7, 8], <div className="pure-menu">
[0, 3, 6], <ul>{choicesToRender}</ul>
[1, 4, 7], </div>
[2, 5, 8], <div>{button}</div>
[0, 4, 8], </div>
[2, 4, 6], );
]; }
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i]; renderSpecifyChoices() {
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { let input = this.renderChoiceInputField();
return squares[a]; 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"));