of
Project II(506)
Submitted By:
Name : Y o n a s A s h a g r i e
Submitted
To
November 29
TABLE OF CONTENTS
1
INTRODUCTION 6
b) Introduction of project 6
Key Features: 7
c) Problem Definition 7
d) Limitations of Existing System 7
e) Objective of the Project 8
SYSTEM ANALYSIS 8
a) Feasibility Study 8
Technical Feasibility 8
Economic Feasibility 9
Legal and Regulatory Feasibility 9
Operational Feasibility 9
b) Hardware and Software Requirements 9
Software Requirements: 9
Hardware Requirements: 10
DEVELOPMENT ENVIRONMENT 10
a) Introduction to Technologies Used 10
● Python: 10
● Pygame: 10
● PyOpenGL: 11
● Numpy: 11
● Quaternion Mathematics: 11
● Visual Studio Code (VS Code): 11
● Additional Libraries: 11
SYSTEM DESIGN 11
a) Modular Description 11
IMPLEMENTATION AND TESTING 12
a) Implementation and screenshots: 12
//Save file as cube_interactive.py 12
//Save file as projection.py 32
b) Testing 41
Key Testing Focus Areas 42
INTRODUCTION
The Rubik's Cube simulator is a Python-based application designed to visualize and interact
with a 3D representation of a Rubik's Cube. Using the powerful matplotlib library, the
simulator provides users with the ability to manipulate the cube's faces and observe the
resulting changes in real time. The implementation leverages quaternion mathematics to
facilitate smooth and accurate rotations, ensuring a realistic user experience.
This simulator not only serves as an educational tool for understanding the mechanics of the
Rubik's Cube but also offers a platform for further exploration into algorithms for solving the
cube. The program's modular design allows for easy enhancements, making it suitable for
both
casual users and advanced programmers interested in algorithm development or graphical
programming.
Key Features:
By offering a comprehensive environment for exploring the Rubik's Cube, this simulator
contributes to a deeper understanding of both the puzzle itself and the underlying
programming techniques used to create it.
b) Problem Definition
The Rubik's Cube, a popular 3D puzzle, often frustrates individuals who struggle with its
mechanics and solving techniques. Traditional resources, such as books and videos,
frequently lack the interactivity needed to engage learners effectively.
This project aims to address this gap by developing an interactive Rubik's Cube simulator
that includes:
By facilitating hands-on learning, this tool will make the Rubik's Cube more approachable for
beginners, promoting deeper engagement with essential problem-solving techniques in
mathematics and computer science.
The current methods for learning to solve the Rubik's Cube exhibit several limitations that
hinder effective understanding and engagement among users:
The primary objective of this project is to develop an interactive Rubik's Cube simulator that
enhances the learning experience for users aspiring to master the puzzle. Key objectives
include:
SYSTEM ANALYSIS
a) Feasibility Study
The feasibility study for the Rubik’s Cube simulator project includes assessments across
technical, economic, legal, and operational aspects to ensure a successful launch.
Technical Feasibility
● Technology Stack: The simulator will be developed in Python, leveraging libraries
for 3D graphics and user interaction to achieve smooth rotations and realistic cube
transformations.
● Integration: Will assess compatibility with educational and gaming platforms,
enabling potential integration for broader reach.
● Security and Compliance: Implements secure handling of user inputs and data
privacy features.
Economic Feasibility
● Budget:
○ Development Costs: Estimated at $5,000 for design, $10,000 for coding, and
$3,000 for testing.
○ Operational Costs: Maintenance, updates, and support will require approximately
$2,000 per year.
○ Marketing Costs: Promotion and user acquisition estimated at $2,000.
● Funding: Potential revenue through educational partnerships, advertising,
and in-app purchases.
Operational Feasibility
● Project Management: 10 days for design, 10 days for front-end, and 24 days
for back-end development.
● Team: A team of 2 developers and a project manager.
● Maintenance: One developer for ongoing support.
● User Experience: Ensures a responsive, engaging user interface for both
desktop and mobile.
The hardware and software requirements for developing a Rubik’s Cube simulator are
outlined below. These requirements may vary based on the system’s complexity and
interactive features.
Software Requirements:
Hardware Requirements:
DEVELOPMENT ENVIRONMENT
The Rubik’s Cube simulator project leverages a variety of tools and technologies to create a
realistic and interactive experience, primarily using Python-based libraries and graphical
tools. The following highlights each technology's role.
● Python:
Python is a versatile programming language, ideal for this project due to its simplicity,
extensive library support, and strong computational capabilities. As the foundation for
the Rubik’s Cube simulator, Python efficiently handles the complex operations
involved in 3D transformations, making it suitable for implementing realistic
movement and rotation calculations.
● Pygame:
Pygame, a popular Python library for multimedia applications, is central to rendering
the Rubik’s Cube in a 3D space. Pygame's core functionalities—like handling
real-time graphics, user inputs, and animations—allow the simulator to deliver a
smooth and
responsive experience. With Pygame, users can interact with the cube, simulating
real-time moves that mimic physical rotation.
● PyOpenGL:
PyOpenGL, which binds the OpenGL library to Python, allows high-performance 3D
rendering by tapping into the GPU’s processing power. It enhances visual quality and
ensures smooth rotations for the cube's transformations. The support for OpenGL’s
complex 3D transformation, lighting, and depth processing features brings a realistic,
high-quality display that feels immersive.
● Numpy:
Numpy, a mathematical library in Python, is employed to handle 3D coordinates and
transformations. It simplifies complex matrix and vector operations, which are
essential to achieving realistic cube rotations. Numpy’s efficient handling of
multidimensional data also optimizes the computational aspects of the simulator,
providing accuracy and speed to rotation and transformation calculations.
● Quaternion Mathematics:
Quaternion mathematics is used to handle smooth and accurate 3D rotations,
avoiding common issues such as gimbal lock that arise with traditional rotation
methods. This approach ensures that every turn or twist of the Rubik's Cube feels
intuitive and smooth, providing a stable rotation system crucial for realistic
simulations.
● Visual Studio Code (VS Code):
Visual Studio Code (VS Code) is the integrated development environment (IDE) for
the project, supporting essential tools like Git version control, syntax highlighting, and
debugging. VS Code enhances productivity, allowing easy testing and debugging,
which is crucial for a project that requires fine-tuning graphics and functionality.
● Git:
Git is the version control system used to manage and track code changes throughout
development. It enables version history and safe experimentation with features,
which are valuable for ensuring project stability and supporting collaboration if the
project scales to a larger team.
● Additional Libraries:
Libraries like Matplotlib and JSON enhance the project by supporting data
visualization (tracking rotation data) and configuration management, providing
flexibility for user settings or future enhancements.
SYSTEM DESIGN
a) Modular Description
import numpy as np
widgets
"""
Sticker representation
Colors are accounted for using color indices and a look-up table.
centroids.shape = (6 * N *
N, 4) faces.shape = (6 * N *
N, 5, 3)
stickers.shape = (6 * N * N, 9,
3) colors.shape = (6 * N * N,)
ind = np.lexsort(centroids.T)
"""
class Cube:
"""Magic
Cube
Representation""" # define
some attribues
default_plastic_color = 'black'
"#00008f",
"#009f0f",
"#ff6f00",
"#cf0000",
"gray"
, "none"] base_face
= np.array([[1, 1, 1],
[-1, 1, 1],
stickerwidth = 0.9
stickerthickness = 0.001
1 - 2 * stickermargin,
1 + stickerthickness)
base_sticker_centroid = np.array([[0, 0, 1 +
stickerthickness]])
# Define rotation angles and axes for the six sides of the
cube x, y, z = np.eye(3)
rots = [Quaternion.from_v_theta(np.eye(3)[0],
rots +=
[Quaternion.from_v_theta(np.eye(3)[1], theta)
np.pi)]
R=x, L=-x,
U=y, D=-y)
face_colors=None): self.N = N
if plastic_color is None:
self.plastic_color = self.default_plastic_color
else:
self.plastic_color = plastic_color
if face_colors is None:
self.face_colors =
self.default_face_colors
else:
self.face_colors = face_colors
self._move_list = []
self._initialize_arrays()
def _initialize_arrays(self):
# base for each one, and then translate & rotate them into position.
cubie_width = 2. / self.N
-1 + (j + 0.5) *
range(self.N)
for j in range(self.N)])
colors face_centroids = []
faces = []
sticker_centroids = []
stickers = []
colors = []
factor = np.array([1. / self.N, 1. / self.N, 1])
for i in range(6):
M = self.rots[i].as_rotation_matrix()
faces_t = np.dot(factor *
self.base_face
+ translations, M.T)
+ translations, M.T)
face_centroids_t = np.dot(self.base_face_centroid
+ translations, M.T)
sticker_centroids_t =
np.dot(self.base_sticker_centroid
+ translations, M.T)
colors_i[:, None]])
faces.append(faces_t)
face_centroids.append(face_centroids_t)
stickers.append(stickers_t)
sticker_centroids.append(sticker_centroids_t)
colors.append(colors_i)
self._face_centroids = np.vstack(face_centroids)
self._faces = np.vstack(faces)
self._sticker_centroids = np.vstack(sticker_centroids)
self._stickers = np.vstack(stickers)
self._colors = np.concatenate(colors)
self._sort_faces()
def _sort_faces(self):
self._face_centroids = self._face_centroids[ind]
self._sticker_centroids = self._sticker_centroids[ind]
self._stickers = self._stickers[ind]
self._colors =
self._colors[ind]
self._faces
= self._faces[ind]
"""Rotate Face"""
self._move_list[-1] except:
if abs(ntot - 4) <
if np.allclose(ntot, 0):
self._move_list =
self._move_list[:-1] else:
layer)
else:
self._move_list.append((f, n, layer))
v = self.facesdict[f]
r = Quaternion.from_v_theta(v, n * np.pi /
2) M = r.as_rotation_matrix()
cubie_width = 2. / self.N
* cubie_width))
for x in [self._stickers, self._sticker_centroids,
self._faces]:
M.T)
def draw_interactive(self):
fig.add_axes(InteractiveCube(s
class
InteractiveCube(plt.Axes):
interactive=True,
view=(0, 0, 10),
**kwa
r gs): if
cube is
None:
self.cube = Cube(3)
elif isinstance(cube,
Cube): self.cube =
cube
else:
self.cube = Cube(cube)
self._view = view
-np.pi / 6)
if fig is
None: fig
plt.gcf()
callbacks =
fig.canvas.callbacks.callbacks del
callbacks['key_press_event']
kwargs.update(dict(aspect=kwargs.get('aspect
', 'equal'),
frameon=kwargs.get('frameon',
False), xticks=kwargs.get('xticks',
self.xaxis.set_major_formatter(plt.NullFormatter())
self.yaxis.set_major_formatter(plt.NullFormatter())
self._start_xlim
= kwargs['xlim']
self._start_ylim
= kwargs['ylim']
self._step_UD = 0.01
self._step_LR = 0.01
self._ax_LR_alt = (0, 0, 1)
self._sticker_polys = None
self._draw_cube()
self.figure.canvas.mpl_connect('button_press_event',
self._mouse_press)
self.figure.canvas.mpl_connect('button_release_even
t ',
self._mouse_release)
self.figure.canvas.mpl_connect('motion_notify_event',
self._mouse_motion)
self.figure.canvas.mpl_connect('key_press_event',
self._key_press)
self.figure.canvas.mpl_connect('key_release_event',
self._key_release)
self._initialize_widgets()
# write some
instructions
self.figure.text(0.05,
0.05,
counter-clockwise)", size=10)
def _initialize_widgets(self):
self._btn_solve.on_clicked(self._solve_cube)
def _draw_cube(self):
stickers = self._project(self.cube._stickers)[:, :,
:2]
plastic_color = self.cube.plastic_color
colors = np.asarray(self.cube.face_colors)[self.cube._colors]
face_zorders = -face_centroids[:, 2]
sticker_zorders = -sticker_centroids[:, 2]
if self._face_polys is None:
# initial call: create polygon objects and add to axes
self._face_polys = []
self._sticker_polys = []
for i in range(len(colors)):
fp = plt.Polygon(faces[i], facecolor=plastic_color,
zorder=face_zorders[i])
sp = plt.Polygon(stickers[i], facecolor=colors[i],
zorder=sticker_zorders[i])
self._face_polys.append(f
p)
self._sticker_polys.append
(sp) self.add_patch(fp)
self.add_patch(sp)
else:
self._face_polys[i].set_xy(faces[i])
self._face_polys[i].set_zorder(face_zorders[i])
self._face_polys[i].set_facecolor(plastic_color)
self._sticker_polys[i].set_xy(stickers[i])
self._sticker_polys[i].set_zorder(sticker_zorders[i
]) self._sticker_polys[i].set_facecolor(colors[i])
self.figure.canvas.draw()
for i in range(steps):
self.cube.rotate_face(face, turns * 1. /
steps,
layer=la
yer)
self._draw_cube()
self.set_xlim(self._start_xlim
self.set_ylim(self._start_ylim
) self._current_rot =
self._start_rot
self._draw_cube()
move_list =
self.cube._move_list[:] for
(face, n, layer) in
move_list[::-1]:
self.rotate_face(face, -n, layer, steps=3)
self.cube._move_list = []
def _key_press(self, event):
events""" if event.key ==
'shift':
self._shift =
True elif
event.key.isdigit():
self._digit_flags[int(event.key)]
if
self._shif
t: ax_LR
self._ax_LR_alt else:
ax_LR = self._ax_LR
self.rotate(Quaternion.from_v_theta(ax_LR,
5 * self._step_LR))
elif event.key ==
'left': if
self._shift:
ax_LR =
self._ax_LR_alt
else:
ax_LR = self._ax_LR
self.rotate(Quaternion.from_v_theta(ax_LR,
-5 * self._step_LR))
self.rotate(Quaternion.from_v_theta(self._ax_UD,
-5 *
'LRUDBF':
if self._shift:
direction = -1
else:
direction = 1
if np.any(self._digit_flags[:N]):
for d in np.arange(N)[self._digit_flags[:N]]:
self.rotate_face(event.key.upper(), direction,
layer=d)
else:
self.rotate_face(event.key.upper(), direction)
self._draw_cube()
self._shift =
False elif
event.key.isdigit():
self._digit_flags[int(event.key)] = 0
def _mouse_press(self, event):
event.y)
if event.button == 1:
self._button1 = True
elif event.button == 3:
self._button2 = True
if event.button == 1:
self._button1 = False
elif event.button == 3:
self._button2 = False
def
_mouse_motion(self,
mouse motion""" if
self._button1 or
self._button2:
dx = event.x -
self._event_xy[0] dy =
event.y - self._event_xy[1]
self._ax_LR_alt else:
ax_LR = self._ax_LR
rot1 = Quaternion.from_v_theta(self._ax_UD,
self._step_UD
* dy) rot2 =
Quaternion.from_v_theta(ax_LR,
self._step_LR * dx)
self.rotate(rot1 * rot2)
self._draw_cube()
if self._button2:
+ dy) xlim =
self.get_xlim() ylim =
self.get_ylim()
* ylim[1])
self.figure.canvas.draw()
if name == '
N=3
c = Cube(N)
# do a 3-corner swap
#c.rotate_face('R')
#c.rotate_face('D')
#c.rotate_face('R',
-1) #c.rotate_face('U'
, -1)
#c.rotate_face('R'
#c.rotate_face('D'
, -1)
#c.rotate_face('R'
, -1)
#c.rotate_face('U'
c.draw_interactive()
plt.show()