From 202626312ec47ae49e72da8db4f921a177acae8a Mon Sep 17 00:00:00 2001 From: ctritta Date: Tue, 5 Feb 2019 14:25:32 +0100 Subject: [PATCH 1/7] first commit --- src/App.css | 44 +++++++++++++------------------------------- src/App.js | 28 ---------------------------- src/App.test.js | 4 ++-- src/Board.js | 41 +++++++++++++++++++++++++++++++++++++++++ src/Cell.js | 12 ++++++++++++ src/Game.js | 21 +++++++++++++++++++++ src/index.js | 4 ++-- 7 files changed, 91 insertions(+), 63 deletions(-) delete mode 100644 src/App.js create mode 100644 src/Board.js create mode 100644 src/Cell.js create mode 100644 src/Game.js diff --git a/src/App.css b/src/App.css index 92f956e..dcf3a4e 100644 --- a/src/App.css +++ b/src/App.css @@ -1,32 +1,14 @@ -.App { +.cell{ + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; text-align: center; -} - -.App-logo { - animation: App-logo-spin infinite 20s linear; - height: 40vmin; -} - -.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; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} + width: 34px; +} \ No newline at end of file diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 7e261ca..0000000 --- a/src/App.js +++ /dev/null @@ -1,28 +0,0 @@ -import React, { Component } from 'react'; -import logo from './logo.svg'; -import './App.css'; - -class App extends Component { - render() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); - } -} - -export default App; diff --git a/src/App.test.js b/src/App.test.js index a754b20..6614dc1 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -1,9 +1,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import App from './App'; +import Game from './Game'; it('renders without crashing', () => { const div = document.createElement('div'); - ReactDOM.render(, div); + ReactDOM.render(, div); ReactDOM.unmountComponentAtNode(div); }); diff --git a/src/Board.js b/src/Board.js new file mode 100644 index 0000000..4250e3c --- /dev/null +++ b/src/Board.js @@ -0,0 +1,41 @@ +import React, { Component } from 'react'; +import Cell from './Cell'; + +export default class Board extends Component { + static renderSquare(i) { + return ; + } + + render() { + const status = 'Next player: X'; + + return ( +
+
+ {status} +
+ + + + + + + + + + + + + + + + + + + + +
{Board.renderSquare(0)}{Board.renderSquare(1)}{Board.renderSquare(2)}
{Board.renderSquare(3)}{Board.renderSquare(4)}{Board.renderSquare(5)}
{Board.renderSquare(6)}{Board.renderSquare(7)}{Board.renderSquare(8)}
+
+ ); + } +} \ No newline at end of file diff --git a/src/Cell.js b/src/Cell.js new file mode 100644 index 0000000..a120293 --- /dev/null +++ b/src/Cell.js @@ -0,0 +1,12 @@ +import React, { Component } from 'react'; + + +export default class Cell extends Component { + render() { + return ( + + ); + } +} \ No newline at end of file diff --git a/src/Game.js b/src/Game.js new file mode 100644 index 0000000..67e02e1 --- /dev/null +++ b/src/Game.js @@ -0,0 +1,21 @@ +import React, { Component } from 'react'; +import './App.css'; +import Board from './Board' + +class Game extends Component { + render() { + return ( +
+
+ +
+
+
{/* status */}
+
    {/* TODO */}
+
+
+ ); + } +} + +export default Game; diff --git a/src/index.js b/src/index.js index 0c5e75d..97b317b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; -import App from './App'; +import Game from './Game'; import * as serviceWorker from './serviceWorker'; -ReactDOM.render(, document.getElementById('root')); +ReactDOM.render(, document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. From 8e328f65474f1c7723248f0a83601154cf50d1eb Mon Sep 17 00:00:00 2001 From: ctritta Date: Tue, 5 Feb 2019 14:41:47 +0100 Subject: [PATCH 2/7] step 3 --- src/App.css | 8 ++++++++ src/Cell.js | 12 ++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/App.css b/src/App.css index dcf3a4e..cf260a7 100644 --- a/src/App.css +++ b/src/App.css @@ -11,4 +11,12 @@ padding: 0; text-align: center; width: 34px; +} + +.cell:focus { + outline: none; +} + +.kbd-navigation .cell:focus { + background: #ddd; } \ No newline at end of file diff --git a/src/Cell.js b/src/Cell.js index a120293..0f2da6b 100644 --- a/src/Cell.js +++ b/src/Cell.js @@ -2,10 +2,18 @@ import React, { Component } from 'react'; export default class Cell extends Component { + + constructor(props) { + super(props); + this.state = { + value: null, + }; + } + render() { return ( - ); } From 9e255ecdbcf90f35586ebc5ef4e861c1c09ee769 Mon Sep 17 00:00:00 2001 From: ctritta Date: Tue, 5 Feb 2019 15:51:07 +0100 Subject: [PATCH 3/7] step 4 --- src/Board.js | 41 ---------------------------- src/Board.jsx | 56 +++++++++++++++++++++++++++++++++++++++ src/{Cell.js => Cell.jsx} | 4 +-- src/{Game.js => Game.jsx} | 0 4 files changed, 58 insertions(+), 43 deletions(-) delete mode 100644 src/Board.js create mode 100644 src/Board.jsx rename src/{Cell.js => Cell.jsx} (70%) rename src/{Game.js => Game.jsx} (100%) diff --git a/src/Board.js b/src/Board.js deleted file mode 100644 index 4250e3c..0000000 --- a/src/Board.js +++ /dev/null @@ -1,41 +0,0 @@ -import React, { Component } from 'react'; -import Cell from './Cell'; - -export default class Board extends Component { - static renderSquare(i) { - return ; - } - - render() { - const status = 'Next player: X'; - - return ( -
-
- {status} -
- - - - - - - - - - - - - - - - - - - - -
{Board.renderSquare(0)}{Board.renderSquare(1)}{Board.renderSquare(2)}
{Board.renderSquare(3)}{Board.renderSquare(4)}{Board.renderSquare(5)}
{Board.renderSquare(6)}{Board.renderSquare(7)}{Board.renderSquare(8)}
-
- ); - } -} \ No newline at end of file diff --git a/src/Board.jsx b/src/Board.jsx new file mode 100644 index 0000000..708cc31 --- /dev/null +++ b/src/Board.jsx @@ -0,0 +1,56 @@ +import React, { Component } from 'react'; +import Cell from './Cell'; + +export default class Board extends Component { + + constructor(props) { + super(props); + this.state = { + cells: Array(9).fill(null), + }; + } + + + renderCell(i) { + return this.handleClick(i)}/>; + } + + handleClick(i) { + const cells = this.state.cells.slice(); + cells[i] = 'X'; + this.setState({cells: cells}); + } + + render() { + const status = 'Next player: X'; + + return ( +
+
+ {status} +
+ + + + + + + + + + + + + + + + + + + + +
{this.renderCell(0)}{this.renderCell(1)}{this.renderCell(2)}
{this.renderCell(3)}{this.renderCell(4)}{this.renderCell(5)}
{this.renderCell(6)}{this.renderCell(7)}{this.renderCell(8)}
+
+ ); + } +} \ No newline at end of file diff --git a/src/Cell.js b/src/Cell.jsx similarity index 70% rename from src/Cell.js rename to src/Cell.jsx index 0f2da6b..2ec792a 100644 --- a/src/Cell.js +++ b/src/Cell.jsx @@ -12,8 +12,8 @@ export default class Cell extends Component { render() { return ( - ); } diff --git a/src/Game.js b/src/Game.jsx similarity index 100% rename from src/Game.js rename to src/Game.jsx From 9d64faa8fd2fd5e66720c0359d552eca66bd4ec1 Mon Sep 17 00:00:00 2001 From: ctritta Date: Tue, 5 Feb 2019 16:23:36 +0100 Subject: [PATCH 4/7] step 5 --- package.json | 6 +++++- src/Board.jsx | 39 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 87d386e..899b096 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,9 @@ "not dead", "not ie <= 11", "not op_mini all" - ] + ], + "devDependencies": { + "enzyme": "^3.8.0", + "enzyme-adapter-react-16": "^1.9.0" + } } diff --git a/src/Board.jsx b/src/Board.jsx index 708cc31..496e072 100644 --- a/src/Board.jsx +++ b/src/Board.jsx @@ -7,6 +7,7 @@ export default class Board extends Component { super(props); this.state = { cells: Array(9).fill(null), + xIsNext: true, }; } @@ -17,12 +18,44 @@ export default class Board extends Component { handleClick(i) { const cells = this.state.cells.slice(); - cells[i] = 'X'; - this.setState({cells: cells}); + if (this.calculateWinner(cells) || cells[i]) + return; + cells[i] = this.state.xIsNext ? 'X' : 'O'; + this.setState({ + cells: cells, + xIsNext: !this.state.xIsNext + }); + } + + calculateWinner(squares) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ]; + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i]; + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a]; + } + } + return null; } render() { - const status = 'Next player: X'; + const winner = this.calculateWinner(this.state.cells); + var status; + + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + } return (
From 501bc9274632a1b9a6c60f0e0dde3635a13a0f54 Mon Sep 17 00:00:00 2001 From: ctritta Date: Wed, 6 Feb 2019 11:54:04 +0100 Subject: [PATCH 5/7] reformat code --- src/Board.jsx | 50 +------------------------------ src/BoardGrid.test.jsx | 60 +++++++++++++++++++++++++++++++++++++ src/Cell.jsx | 7 ----- src/Game.jsx | 67 +++++++++++++++++++++++++++++++++++++++--- src/setupTests.js | 3 ++ 5 files changed, 127 insertions(+), 60 deletions(-) create mode 100644 src/BoardGrid.test.jsx create mode 100644 src/setupTests.js diff --git a/src/Board.jsx b/src/Board.jsx index 496e072..b649f19 100644 --- a/src/Board.jsx +++ b/src/Board.jsx @@ -3,65 +3,17 @@ import Cell from './Cell'; export default class Board extends Component { - constructor(props) { - super(props); - this.state = { - cells: Array(9).fill(null), - xIsNext: true, - }; - } - renderCell(i) { - return this.handleClick(i)}/>; + return this.props.onClick(i)}/>; } - handleClick(i) { - const cells = this.state.cells.slice(); - if (this.calculateWinner(cells) || cells[i]) - return; - cells[i] = this.state.xIsNext ? 'X' : 'O'; - this.setState({ - cells: cells, - xIsNext: !this.state.xIsNext - }); - } - calculateWinner(squares) { - const lines = [ - [0, 1, 2], - [3, 4, 5], - [6, 7, 8], - [0, 3, 6], - [1, 4, 7], - [2, 5, 8], - [0, 4, 8], - [2, 4, 6], - ]; - for (let i = 0; i < lines.length; i++) { - const [a, b, c] = lines[i]; - if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { - return squares[a]; - } - } - return null; - } render() { - const winner = this.calculateWinner(this.state.cells); - var status; - - if (winner) { - status = 'Winner: ' + winner; - } else { - status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); - } return (
-
- {status} -
diff --git a/src/BoardGrid.test.jsx b/src/BoardGrid.test.jsx new file mode 100644 index 0000000..1d28a7d --- /dev/null +++ b/src/BoardGrid.test.jsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import Board from './Board'; + +describe('BoardGrid tests', () => { + let wrapper; + beforeEach(() => { + wrapper = shallow(); + }); + + describe('isBoardFull', () => { + it('Should return false at startup', () => { + expect(wrapper.instance().isBoardFull()).toBe(false); + }); + + it('Should return true if board is full', () => { + wrapper.setState({ grid: Array(9).fill('Y') }); + + expect(wrapper.instance().isBoardFull()).toBe(true); + }); + }); + + describe('getWinner', () => { + it('Should return null at startup', () => { + expect(wrapper.instance().getWinner()).toBe(null); + }); + + it('Should return X when first player has fullfill firt row', () => { + wrapper.setState({ + grid: Array(3).fill('X') + }); + + expect(wrapper.instance().getWinner()).toBe('X'); + }); + + it('Should return O when second player has fullfill firt column', () => { + wrapper.setState({ + grid: [ + 'O', null, null, + 'O', null, null, + 'O', null, null + ] + }); + + expect(wrapper.instance().getWinner()).toBe('O'); + }); + + it('Should return X when first player has fullfill diagonal', () => { + wrapper.setState({ + grid: [ + 'O', null, 'X', + 'O', 'X', null, + 'X', null, 'O' + ] + }); + + expect(wrapper.instance().getWinner()).toBe('X'); + }); + }); +}); diff --git a/src/Cell.jsx b/src/Cell.jsx index 2ec792a..5c63274 100644 --- a/src/Cell.jsx +++ b/src/Cell.jsx @@ -3,13 +3,6 @@ import React, { Component } from 'react'; export default class Cell extends Component { - constructor(props) { - super(props); - this.state = { - value: null, - }; - } - render() { return ( + + ); + }); + let status; if (winner) { status = 'Winner: ' + winner; @@ -71,6 +91,7 @@ class Game extends Component {
{status}
+
    {moves}
); From 73c54ebcdf0f43a7741b8aa816ae87f802867cb1 Mon Sep 17 00:00:00 2001 From: ctritta Date: Wed, 6 Feb 2019 15:49:09 +0100 Subject: [PATCH 7/7] design --- src/App.css | 43 +++++++++++++++++++++++++++++++------------ src/Game.jsx | 4 ++-- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/App.css b/src/App.css index cf260a7..41a8699 100644 --- a/src/App.css +++ b/src/App.css @@ -1,16 +1,35 @@ + +.game { + position: absolute; + z-index: 15; + top: 25%; + left: 50%; + margin: -100px 0 0 -150px;} + + + .cell{ - background: #fff; - border: 1px solid #999; - float: left; - font-size: 24px; - font-weight: bold; - line-height: 34px; - height: 34px; - margin-right: -1px; - margin-top: -1px; - padding: 0; - text-align: center; - width: 34px; + + border: 1px solid #999; + float: left; + font-size: 60px; + font-family: Ubuntu, sans-serif; + color:white; + font-weight: bold; + line-height: 34px; + height: 100px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 100px; + border-radius: 14px; + background: #78bec5; +} + + +.game-info { + margin-left: 40px; } .cell:focus { diff --git a/src/Game.jsx b/src/Game.jsx index b214e9d..8b0f7e5 100644 --- a/src/Game.jsx +++ b/src/Game.jsx @@ -89,10 +89,10 @@ class Game extends Component { this.handleClick(i)}/> -
+
{status}
    {moves}
-
+
); }