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/App.css b/src/App.css index 92f956e..41a8699 100644 --- a/src/App.css +++ b/src/App.css @@ -1,32 +1,41 @@ -.App { - text-align: center; -} -.App-logo { - animation: App-logo-spin infinite 20s linear; - height: 40vmin; -} +.game { + position: absolute; + z-index: 15; + top: 25%; + left: 50%; + margin: -100px 0 0 -150px;} + + -.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; +.cell{ + + 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; } -.App-link { - color: #61dafb; + +.game-info { + margin-left: 40px; } -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } +.cell:focus { + outline: none; } + +.kbd-navigation .cell:focus { + background: #ddd; +} \ 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.jsx b/src/Board.jsx new file mode 100644 index 0000000..b649f19 --- /dev/null +++ b/src/Board.jsx @@ -0,0 +1,41 @@ +import React, { Component } from 'react'; +import Cell from './Cell'; + +export default class Board extends Component { + + + renderCell(i) { + return this.props.onClick(i)}/>; + } + + + + render() { + + return ( +
+ + + + + + + + + + + + + + + + + + + + +
{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/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 new file mode 100644 index 0000000..5c63274 --- /dev/null +++ b/src/Cell.jsx @@ -0,0 +1,13 @@ +import React, { Component } from 'react'; + + +export default class Cell extends Component { + + render() { + return ( + + ); + } +} \ No newline at end of file diff --git a/src/Game.jsx b/src/Game.jsx new file mode 100644 index 0000000..8b0f7e5 --- /dev/null +++ b/src/Game.jsx @@ -0,0 +1,101 @@ +import React, { Component } from 'react'; +import './App.css'; +import Board from './Board' + +class Game extends Component { + + constructor(props) { + super(props); + this.state = { + history: [{ + cells: Array(9).fill(null), + }], + xIsNext: true, + stepNumber: 0, + }; + } + + handleClick(i) { + const history = this.state.history.slice(0, this.state.stepNumber + 1); + const current = history[history.length - 1]; + const cells = current.cells.slice(); + if (this.calculateWinner(cells) || cells[i]) + return; + cells[i] = this.state.xIsNext ? 'X' : 'O'; + this.setState({ + history: history.concat([{ + cells: cells, + }]), + xIsNext: !this.state.xIsNext, + stepNumber: history.length, + }); + } + + jumpTo(step) { + this.setState({ + stepNumber: step, + xIsNext: (step % 2) === 0, + }); + } + + + 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 history = this.state.history; + const current = history[this.state.stepNumber]; + const winner = this.calculateWinner(current.cells); + + const moves = history.map((step, move) => { + const desc = move ? + 'Go to move #' + move : + 'Go to game start'; + return ( +
  • + +
  • + ); + }); + + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + } + + + return ( +
    +
    + this.handleClick(i)}/> +
    +
    +
    {status}
    +
      {moves}
    +
    +
    + ); + } +} + +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. diff --git a/src/setupTests.js b/src/setupTests.js new file mode 100644 index 0000000..2d30267 --- /dev/null +++ b/src/setupTests.js @@ -0,0 +1,3 @@ +import {configure} from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +configure({adapter: new Adapter()}); \ No newline at end of file