Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/images/game-of-life.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/app/components/algorithm-cards.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ const algorithms = [
title: 'Binary Search',
description: "Binary search is an efficient algorithm for finding an item from a sorted list of item",
image: '/AlgorithmVisualizer/images/binary-search.png?height=200&width=300'
},{
id: 'game-of-life',
title: 'Game of Life',
description: "Visualize the Game of Life cellular automaton",
image: '/AlgorithmVisualizer/images/game-of-life.png?height=200&width=300'
},
// {
// id: '15-puzzle',
Expand Down
10 changes: 10 additions & 0 deletions src/app/game-of-life/grid.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

.Grid{
font-size: 0;
}
div{
padding: 0px;
margin: 0px;
margin-bottom: 0px;
padding-bottom: 0px;
}
32 changes: 32 additions & 0 deletions src/app/game-of-life/grid.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

import './grid.css';
import Node from "./node";


export default function Grid({ grid, onMouseDown, onMouseEnter, onMouseUp }) {
return (
<div className="Grid">
{grid.map((row, rowidx) => {
return (
<div key={rowidx}>
{row.map((node, nodeidx) => {
const { row, col, isAlive } = node;
return (
<Node
key={nodeidx}
row={row}
col={col}
node={node}
isAlive={isAlive}
onMouseDown={onMouseDown}
onMouseEnter={onMouseEnter}
onMouseUp={onMouseUp}
/>
);
})}
</div>
);
})}
</div>
);
}
21 changes: 21 additions & 0 deletions src/app/game-of-life/menu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Button } from '@/components/ui/button';
import PropTypes from 'prop-types';

export default function Menu({ onStart, onStop, onClear }) {
return (
<div className="w-64 bg-gray-100 p-4 space-y-6">
<h2 className="text-lg font-semibold">Settings</h2>

{/* <Button className="m-1" >Clear Path</Button> */}
<Button className="m-1" onClick={onClear}>Clear Board</Button>
<Button className="w-full" onClick={onStop} >Stop Simulation</Button>
<Button className="w-full" onClick={onStart} >Start Simulation</Button>
</div>
);
}

Menu.propTypes = {
onStart: PropTypes.func.isRequired,
onStop: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
};
100 changes: 100 additions & 0 deletions src/app/game-of-life/node.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
.node{
height:25px;
width:25px;
background-color: white;
outline:1px solid rgb(175, 216, 248);
display: inline-block;
}

.node-start{
background-color: chartreuse;
}
.node-end{
background-color: brown;
}
.node-visited{
animation-name: visitedAnimation;
animation-iteration-count: 1;
animation-duration: 1.5s;
animation-delay: 0;
background-color: rgba(0, 190, 218, 0.75);
}

@keyframes visitedAnimation {
0% {
transform: scale(0.3);
background-color: rgba(0, 0, 66, 0.75);
border-radius: 100%;
}

50% {
background-color: rgba(17, 104, 217, 0.75);
}

75% {
transform: scale(1.2);
background-color: rgba(0, 217, 159, 0.75);
}

100% {
transform: scale(1);
background-color: rgba(0, 190, 218, 0.75);
}
}

.node-wall {
background-color: black;
outline: 1px solid black;
/* animation-name: wallAnimation;
animation-duration: 0.3s;
animation-timing-function: ease-out;
animation-delay: 0;
animation-direction: alternate;
animation-iteration-count: 1;
animation-fill-mode: forwards;
animation-play-state: running; */
}
@keyframes wallAnimation {
0% {
transform: scale(.3);
background-color: rgb(12, 53, 71);
}

50% {
transform: scale(1.2);
background-color: rgb(12, 53, 71);
}

100% {
transform: scale(1.0);
background-color: rgb(12, 53, 71);
}
}

.node-shortest-path {
animation-name: shortestPath;
animation-duration: 1.5s;
animation-timing-function: ease-out;
animation-delay: 0;
animation-direction: alternate;
animation-iteration-count: 1;
animation-fill-mode: forwards;
animation-play-state: running;
}

@keyframes shortestPath {
0% {
transform: scale(0.6);
background-color: rgb(255, 254, 106);
}

50% {
transform: scale(1.2);
background-color: rgb(255, 254, 106);
}

100% {
transform: scale(1);
background-color: rgb(255, 254, 106);
}
}
22 changes: 22 additions & 0 deletions src/app/game-of-life/node.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

import "./node.css";

export default function Node({ node, onMouseDown, onMouseEnter, onMouseUp }) {
return (
<div
id={`node-${node.row}-${node.col}`}
className={getClassName()}
onMouseDown={() => onMouseDown(node.row, node.col)}
onMouseEnter={() => onMouseEnter(node.row, node.col)}
onMouseUp={() => onMouseUp(node.row, node.col)}
/>
);

function getClassName() {
if (node.isAlive === true) {
return "node node-wall";
}else {
return "node";
}
}
}
181 changes: 181 additions & 0 deletions src/app/game-of-life/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
"use client";
import Navbar from '@/components/navbar';
import { createRef, useRef, useState, useEffect } from 'react';
import Menu from "./menu";
import Grid from "./grid";


export default function GameOfLifePage() {

let gridRef = createRef();

const [grid, setGrid] = useState([]);
const [running, setRunning] = useState(false);
const runningRef = useRef(false); // Add this ref


useEffect(() => {
const width = gridRef.current.offsetWidth;
const height = gridRef.current.offsetHeight;
const row = Math.max(Math.floor(height / 25) - 2, 10);
const col = Math.floor(width / 25);
setGrid(getInitialGrid(row, col));
}, []);

const handleMouseDown = (row, col) => {

const newGrid = getNewGridWithWallToggled(grid, row, col);
setGrid(newGrid);

// this.setState({ mouseIsPressed: true });
}

const handleMouseEnter = (row, col) => {
// if (this.state.mouseIsPressed === false) return;
// if ((this.state.startNode.row !== row || this.state.startNode.col !== col) && (this.state.endNode.row !== row || this.state.endNode.col !== col)) {
// const newGrid = getNewGridWithWallToggled(this.state.grid, row, col);
// this.setState({ grid: newGrid });
// }
}

const handleMouseUp = (row, col) => {
// this.setState({ mouseIsPressed: false });
}

const handleStart = () => {
setRunning(true);
runningRef.current = true; // Update ref

gameOfLife();
}

const handleStop = () => {
setRunning(false);
runningRef.current = false;
console.log("Simulation stopped");
}

const handleClearBoard = () => {
setRunning(false);
runningRef.current = false;
const width = gridRef.current.offsetWidth;
const height = gridRef.current.offsetHeight;
const row = Math.max(Math.floor(height / 25) - 2, 10);
const col = Math.floor(width / 25);
setGrid(getInitialGrid(row, col));
}

const gameOfLife = async () => {
let newGrid = getNextGeneration(grid);
while (runningRef.current) {
setGrid(newGrid);
newGrid = getNextGeneration(newGrid);
await sleep(200);
}
}

return (
<div className="flex flex-col h-screen">

<Navbar title="Game of Life" />

<div className="flex flex-1 overflow-hidden">
<Menu
onStart={handleStart}
onStop={handleStop}
onClear={handleClearBoard}
/>

<div className="flex flex-1 flex-col items-center justify-center overflow-auto">
<div className="w-full h-full flex items-center justify-center" ref={gridRef}>
<Grid
grid={grid}
onMouseDown={handleMouseDown}
onMouseEnter={handleMouseEnter}
onMouseUp={handleMouseUp}
/>
</div>
</div>
</div>
</div>
);
}

const getInitialGrid = (row, col) => {
let grid = [];
for (let i = 0; i < row; i++) {
let row = [];
for (let j = 0; j < col; j++) {
row.push(createNode(i, j));
}
grid.push(row);
}
return grid;
}

const createNode = (row, col) => {
return {
row,
col,
isAlive: false
}
}

const getNewGridWithWallToggled = (grid, row, col) => {
const newGrid = grid.slice();
const node = newGrid[row][col];

const newNode = {
...node,
isAlive: !node.isAlive,
};

newGrid[row][col] = newNode;
return newGrid;
};

const getNextGeneration = (grid) => {
const newGrid = grid.slice();
for (let i = 0; i < grid.length; i++) {
newGrid[i] = grid[i].slice();
for (let j = 0; j < grid[i].length; j++) {
const node = grid[i][j];
const aliveNeighbors = getAliveNeighbors(grid, node);

if (node.isAlive && (aliveNeighbors < 2 || aliveNeighbors > 3)) {
newGrid[i][j] = {
...node,
isAlive: false
}
}
if (!node.isAlive && aliveNeighbors === 3) {
newGrid[i][j] = {
...node,
isAlive: true
}
}
}
}
return newGrid;
}

const getAliveNeighbors = (grid, node) => {

const { row, col } = node;
const dirx = [-1, 1, 0, 0, -1, -1, 1, 1];
const diry = [0, 0, -1, 1, -1, 1, -1, 1];
let count = 0;
for (let i = 0; i < 8; i++) {
const newRow = row + dirx[i];
const newCol = col + diry[i];
if (newRow >= 0 && newRow < grid.length && newCol >= 0 && newCol < grid[0].length && grid[newRow][newCol].isAlive) {
count++;
}
}

return count;
}

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Loading
Loading