In this project, I use React, Redux, CSS Grid Layout and the React DnD API to build a clone of the classic math puzzle, Towers of Hanoi. |
Towers of Hanoi consists of three towers and at least three disks of different sizes, which can slide onto any tower. The puzzle starts with the disks in a neat stack in ascending order of size on one tower, the smallest at the top, thus making a conical shape.
The objective of the puzzle is to move the stack to another tower following these simple rules:
- Only one disk can be moved at a time.
- Each move consists of taking the upper disk from one of the stacks and placing it on top of another stack or on an empty tower.
- No larger disk may be placed on top of a smaller disk.
Before attempting to code the solution, my first step is break the problem down into smaller, easier-to-manage parts. Then, I prioritize the project's features.
This analytic approach is modeled by one of Dan Abramov's tutorials.
Dan Abramov – the creator of Redux, Create React App, React DnD – published a chessboard tutorial (where a lonely knight simply moves about the chess board) that inspired much of the analytical process for this project's undertaking. It's also how I came to explore React's drag-and-drop capabilities.Components | Description | Props |
Disk
|
A single disk. | Disk belongs to a tower and are stacked on other disks. Probably needs no props since the towers will be aware of its rank (size). |
DiskContainer
|
Smart wrapper that handles disk logic (e.g. drag-and-drop source). | - |
Tower
|
A single tower. | Tower contains the disks that have been moved to it. Possible props include the disks that belong to it and their ranks since there are rules about how they can be stacked. |
TowerContainer
|
Smart wrapper that handles tower logic (e.g. drag-and-drop target). | - |
Setting
|
Game board consists of three towers. All the playable disks are stacked on the first tower when the game begins. | App will probably pass in some amount of playable disks as a prop. |
App
|
Renders the setting (i.e. game board) with some number of playable disks and wraps it in some state manager (e.g. observer) that updates where the disks are being moved to. | - |
Most likely, in the App
component. It's best practice to have as little state in components as possible. Since Setting
will already contain some layout logic, it's best not to burden it with managing game state as well. Since the Disk
component will need to be draggable, this requires maintaining pertinent disk information in some kind of state storage, then having some way to update it.
Here is my implementation overview (along with an ongoing post-MVP backlog):
-
Implement a responsive, grid system and add minimal styling. ✓
- Build a grid with a floor base and three standing towers. ✓
- Integrate with SASS to handle CSS styling. Post-MVP
- Integrate with Styled Components to handle React component styling. Post-MVP
- Improve header styling and landing view (fade header in, then footer and towers in, finally drop disks into first tower). Post-MVP
-
Implement component(s) for disks and their mechanics, e.g. drag-and-drop feature. ✓
- Upon release, lock to nearest standing tower and drop to the bottom (or top of disk stack).
- Consider using react-dnd's API to handle drag-and-drop for non-touch devices. ✓
- Since native drag-and-drop is not yet supported on touch devices, consider using Yahoo's touch back-end for react-dnd. Post-MVP
- Consider using SyntheticEvent wrappers to handle drag-and-drop without using an external API. Post-MVP
- Implement collision detection between disks and towers so that drag-and-drop can execute properly when disk is hovering the tower (even when cursor is not). Post-MVP
- DropTarget responds only to the cursor, not to the element being dragged. What can be done to change this behavior?
- Generate at least three disks stacked on the first tower. ✓
- Implement difficulty levels according to the number of disks, where the user can select a starting number of disks (the minimum / default is 3). Post-MVP
- Add modal that pops up with game description at time of initial loading. ✓
- Add modal that pops up with results when game is won, including a message about how many moves were taken.
- Upon release, lock to nearest standing tower and drop to the bottom (or top of disk stack).
-
Implement gameplay logic. ✓
- Only the upper (top-most) disk from any tower can be moved. ✓
- Allow only valid moves: ✓
- a disk may be dropped over a larger disk within another tower, ✓
- or a disk may be dropped into an empty tower (containing no disks). ✓
- Game is won when disks are stacked conically on either the second or third tower (the first tower is empty). ✓
- Implement an option to continue to next difficulty level (add one more disk). Post-MVP
- Implement buttons:
- plus / minus buttons to increment / decrement number of playable disks, Post-MVP
- an undo button to move the most recently moved disk back to its previous tower, and a... Post-MVP
- restart button to move all the disks back to the first tower while resetting number of moves played for the round so far. Post-MVP
-
Implement an AI logic component. Post-MVP
- Compare algorithms for any number of disks.
- Design iterative solution.
- Design recursive solution.
- Implement feature where user can click a button at any point of the game and step through the solution. Post-MVP
- Implement feature where user can click a button at any point of the game and animate the remaining portion of the solution. Post-MVP
- Compare algorithms for any number of disks.
-
Integrate with React-Redux to manage state.
-
Test thoroughly for cross-browser compatibility, responsiveness and performance.
- Since I'm taking a desktop-first, responsive design approach, the next step in this regard would be to adapt everything for mobile-readiness. Post-MVP
The project's file tree file tree and a brief note on each file's relevancy:
src/
├── components
│ ├── App.jsx
│ ├── constants
│ │ ├── ItemTypes.js
│ │ └── Layout.js
│ ├── game
│ │ ├── Disk.jsx
│ │ ├── Setting.jsx
│ │ └── Tower.jsx
│ ├── layout
│ │ ├── Footer.jsx
│ │ └── Header.jsx
│ └── util
│ ├── Overlay.jsx
│ └── modals
│ ├── Description.jsx
│ ├── DescriptionModal.jsx
│ ├── Results.jsx
│ └── ResultsModal.jsx
├── index.js
├── lib
│ └── withDragDropContext.js
└── style
└── global.css