Skip to content

Commit 7f18283

Browse files
committed
feat: Implement undo action on back button
When the user clicks on the back button, the last action is undone. This is done through undo-redux that handle the logic. Note a small refacto was necessary, so the last card revealing is no longer a dedicated action but rather handled in the moveCard reducer method, as this was troublesome when undoing action.
1 parent a09d73a commit 7f18283

10 files changed

Lines changed: 51 additions & 37 deletions

File tree

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"react-redux": "^5.0.7",
1414
"react-scripts": "3.4.1",
1515
"redux": "^4.0.0",
16+
"redux-undo": "^1.0.1",
1617
"semantic-ui-css": "^2.4.1",
1718
"semantic-ui-react": "^0.88.2"
1819
},

src/App.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const App = ({ cards, game }) => {
8585
const mapStateToProps = (state) => {
8686
//console.log('state : ', state)
8787
const { cards, game } = state
88-
return { cards, game }
88+
return { cards: cards.present, game }
8989
}
9090

9191
export default connect(mapStateToProps)(App)

src/components/ActionButtons.jsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
import React from 'react'
22
import { connect } from 'react-redux'
3+
import { ActionCreators } from 'redux-undo'
34
import { Segment, Button, Icon } from 'semantic-ui-react'
4-
55
import { cheatMode } from '../redux/actions/actions'
66

77
const mapDispatchToProps = (dispatch) => {
88
return {
99
cheatMode: (id, card) => {
1010
dispatch(cheatMode(card))
11+
},
12+
undo: () => {
13+
dispatch(ActionCreators.undo())
1114
}
1215
}
1316
}
1417

15-
const ActionButtons = ({ game, cheatMode }) => {
18+
const ActionButtons = ({ game, cheatMode, undo }) => {
1619
return (
1720
<Segment>
1821
<Button className="ui red huge circular icon Button">
1922
<Icon name="home"></Icon>
2023
</Button>
21-
<Button className="ui red huge circular icon Button">
24+
<Button className="ui red huge circular icon Button" onClick={undo}>
2225
<Icon name="reply"></Icon>
2326
</Button>
2427
<Button className="ui red huge circular icon Button">

src/components/Card.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { findAutoMoveTarget } from '../game/game'
88
import { moveCard } from '../redux/actions/actions'
99

1010
const mapStateToProps = (state, ownProps) => {
11-
const { cards, game } = state
11+
const game = state.game
12+
const cards = state.cards.present
1213
return {
1314
isLastCard: isLastContainerCard(cards, ownProps),
1415
findTarget: () => {

src/components/Column.jsx

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import React, { useEffect } from 'react'
22
import { connect } from 'react-redux'
33
import { useDrop } from 'react-dnd'
4-
import {
5-
moveCard,
6-
revealLastColumnCard,
7-
checkGameWon
8-
} from '../redux/actions/actions'
4+
import { moveCard, checkGameWon } from '../redux/actions/actions'
95
import { canPlayInColumn } from '../game/game'
106
import Card from './Card'
117
import Empty from './Empty'
128
import { Types } from '../game/consts'
139

1410
const mapStateToProps = (state, ownProps) => {
15-
const { cards, game } = state
11+
const game = state.game
12+
const cards = state.cards.present
1613
return {
1714
getAllColumnsCards: () => cards[Types.COLUMNS],
1815
column: cards[Types.COLUMNS][ownProps.id],
@@ -25,9 +22,6 @@ const mapDispatchToProps = (dispatch) => {
2522
dropCard: (id, card) => {
2623
dispatch(moveCard(card, { type: Types.COLUMNS, id }))
2724
},
28-
makeLastCardVisible: (id) => {
29-
dispatch(revealLastColumnCard(id))
30-
},
3125
checkGameWon: (columns) => {
3226
dispatch(checkGameWon(columns))
3327
}
@@ -39,7 +33,6 @@ const Column = ({
3933
column,
4034
getAllColumnsCards,
4135
dropCard,
42-
makeLastCardVisible,
4336
canDropInColumn,
4437
checkGameWon
4538
}) => {
@@ -54,13 +47,10 @@ const Column = ({
5447
}
5548
})
5649

50+
const allCards = getAllColumnsCards()
5751
useEffect(() => {
58-
// Make last column card visible
59-
if (column.length > 0 && !column[column.length - 1].visible) {
60-
makeLastCardVisible(id)
61-
checkGameWon(getAllColumnsCards())
62-
}
63-
})
52+
checkGameWon(allCards)
53+
}, [checkGameWon, allCards])
6454

6555
const renderCard = (card, position, children) => {
6656
return (

src/components/Foundation.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import { isSingleCardDrop } from '../redux/helpers'
99
import { canPlayInFoundation } from '../game/game'
1010

1111
const mapStateToProps = (state, ownProps) => {
12-
const { cards, game } = state
12+
const game = state.game
13+
const cards = state.cards.present
1314
return {
1415
canDropInFoundation: (item) =>
1516
isSingleCardDrop(cards, item) &&

src/game/init.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export const initDeck = () => {
1414
const columnCards = []
1515
for (let j = 0; j < i + 1; j++) {
1616
const card = pickRandomCard(shuffledDeck)
17+
if (j === i) {
18+
// Last column card
19+
card.visible = true
20+
}
1721
columnCards.push(card)
1822
}
1923
columns.push(columnCards)

src/redux/reducers/cards.js

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import {
88
import { Types } from '../../game/consts'
99
import { getColumnChildCards } from '../helpers'
1010

11+
const revealLastColumnCard = (state, columnId) => {
12+
const columnCards = state[Types.COLUMNS][columnId]
13+
if (columnCards.length > 0) {
14+
columnCards[columnCards.length - 1].visible = true
15+
}
16+
}
17+
1118
// XXX - We use immer to update state to ease state mutations
1219
// When doing a mutation on a nested level, all involved levels must be
1320
// shallow copied, as it keeps references.
@@ -23,17 +30,23 @@ const moveCard = (state, card, destination) => {
2330
}
2431

2532
return produce(state, (draft) => {
26-
if (
27-
card.container.type === Types.COLUMNS ||
28-
card.container.type === Types.FOUNDATION
29-
) {
33+
if (sourceType === Types.COLUMNS || sourceType === Types.FOUNDATION) {
34+
// Remove card from column or foundation
3035
draft[sourceType][card.container.id].splice(
3136
draft[sourceType][card.container.id].length - cards.length
3237
)
3338
} else {
39+
// Remove card from stock
3440
draft[sourceType].splice(draft[sourceType].length - 1)
3541
}
42+
43+
// Add card to destination
3644
draft[targetType][destination.id].push(...cards)
45+
46+
// Make last column card visible
47+
if (sourceType === Types.COLUMNS) {
48+
revealLastColumnCard(draft, card.container.id)
49+
}
3750
})
3851
}
3952

@@ -52,13 +65,6 @@ const refillStock = (state) => {
5265
})
5366
}
5467

55-
const revealLastColumnCard = (state, columnId) => {
56-
return produce(state, (draft) => {
57-
const column = draft.columns[columnId]
58-
draft.columns[columnId][column.length - 1].visible = true
59-
})
60-
}
61-
6268
const cards = (state = {}, action) => {
6369
switch (action.type) {
6470
case MOVE_CARD:

src/redux/reducers/index.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { combineReducers } from 'redux'
2-
import cards from './cards'
3-
import game from './game'
2+
import undoable from 'redux-undo'
3+
import cardsReducer from './cards'
4+
import gameReducer from './game'
45

56
const rootReducer = combineReducers({
6-
cards,
7-
game
7+
cards: undoable(cardsReducer, {
8+
limit: false
9+
}),
10+
game: gameReducer
811
})
912

1013
export default rootReducer

0 commit comments

Comments
 (0)