diff --git a/game_manager.js b/game_manager.js
new file mode 100644
index 0000000..7048f25
--- /dev/null
+++ b/game_manager.js
@@ -0,0 +1,173 @@
+class GameManager
+{
+ constructor(grid, sidebar_div)
+ {
+ this.grid = grid;
+ this.sidebar_div = sidebar_div;
+ this.level = 0;
+ this.blocked = false;
+ this.default_opacity = default_opacity;
+ this.n_selected = 0;
+
+ this.grid.register_click_callback((i,j) => this.click_listener(i,j));
+ }
+
+ register_level_callback(func)
+ {
+ this.level_callback = func;
+ func(0);
+ }
+
+ register_statustext_callback(func)
+ {
+ this.status_callback = func;
+ func("click 'start' to begin a new game");
+ }
+
+ click_listener(x,y)
+ {
+ var i = this.coords2index(y,x); //TODO: change index order
+
+ if (this.level <= 0)
+ {
+ //we're not ingame
+ return;
+ }
+
+ if (this.active_cells.includes(i))
+ {
+ this.n_selected++;
+ if (this.n_selected == this.level)
+ {
+ this.on_level_up();
+ }
+ }
+ else
+ {
+ this.on_game_over();
+ }
+ }
+
+ restart()
+ {
+ if (!this.blocked)
+ {
+ this.sidebar_div.style.backgroundColor = "rgba(0,255,0," + this.default_opacity + ")";
+ this.level = 0;
+ this.level_callback(0);
+ this.grid.deactivate_all();
+ this.inactive_cells = [];
+ var i = 0;
+ for(i = 0; i < this.grid.width * this.grid.height; i++)
+ {
+ this.inactive_cells.push(i);
+ }
+ this.active_cells = [];
+ this.on_level_up();
+ }
+ }
+
+ create_new_sequence()
+ {
+ // get one cell randomly
+ var i = Math.floor(Math.random()*this.inactive_cells.length);
+ var item = this.inactive_cells[i];
+ this.inactive_cells.splice(i, 1);
+
+ this.active_cells.push(item);
+ }
+
+ show_sequence()
+ {
+ this.status_callback("memorize new sequence");
+ this.grid.deactivate_all();
+ var i;
+ for (i=0; i < this.active_cells.length; i++)
+ {
+ var coords = this.index2coord(this.active_cells[i]);
+ var x = coords[1];
+ var y = coords[0];
+
+ this.grid.cells[y][x].activate();
+ }
+ var time = this.level * 300 + 2000;
+ setTimeout(c => this.end_show_sequence(c), time);
+ }
+
+ end_show_sequence()
+ {
+ this.status_callback("it's your turn, reproduce the sequence!");
+ this.sidebar_div.style.backgroundColor = "rgba(0,0,0," + this.default_opacity + ")";
+ this.grid.deactivate_all();
+ this.unblock();
+ }
+
+ on_level_up()
+ {
+ if (this.level >= this.grid.width * this.grid.height)
+ {
+ this.on_win();
+ return;
+ }
+ this.block();
+ this.sidebar_div.style.backgroundColor = "rgba(0,255,0," + this.default_opacity + ")";
+ this.level++;
+ this.n_selected = 0;
+ this.level_callback(this.level);
+ this.create_new_sequence();
+ this.show_sequence();
+ }
+
+ on_game_over()
+ {
+ // activate all correct cells:
+ this.grid.deactivate_all();
+ var i;
+ for (i=0; i < this.active_cells.length; i++)
+ {
+ var coords = this.index2coord(this.active_cells[i]);
+ var x = coords[1];
+ var y = coords[0];
+
+ this.grid.cells[y][x].activate();
+ }
+
+ this.status_callback("Game Over. Won rounds: " + (this.level - 1));
+ this.sidebar_div.style.backgroundColor = "rgba(255,0,0," + this.default_opacity + ")";
+ this.level = 0;
+ this.n_selected = 0;
+ }
+
+ on_win()
+ {
+ this.status_callback("Congratulation, You Won");
+ this.sidebar_div.style.backgroundColor = "rgba(0,255,0," + this.default_opacity + ")";
+ this.level = 0;
+ this.n_selected = 0;
+ }
+
+ block()
+ {
+ this.blocked = true;
+ this.grid.block();
+ }
+
+ unblock()
+ {
+ this.grid.unblock();
+ this.blocked = false;
+ }
+
+ index2coord(i)
+ {
+ var x = i % this.grid.width;
+ var y = Math.floor(i / this.grid.width);
+ var result = [y,x];
+ return result;
+ }
+
+ coords2index(y,x)
+ {
+ return y * this.grid.width + x;
+ }
+}
\ No newline at end of file
diff --git a/grid.js b/grid.js
new file mode 100644
index 0000000..2d924c4
--- /dev/null
+++ b/grid.js
@@ -0,0 +1,97 @@
+class Grid
+{
+ constructor(width, height, grid_container_div, tile_width, tile_height)
+ {
+ this.width = width;
+ this.height = height;
+
+ this.tile_width = tile_width;
+ this.tile_height = tile_height;
+
+ this.cells = []
+
+ this.grid_container_div = grid_container_div
+
+ this.create();
+ }
+
+ create()
+ {
+ var x,y;
+ for (y = 0; y < this.height; y++)
+ {
+ var div_row = this.grid_container_div.appendChild(document.createElement("div"));
+ var row = [];
+ for (x = 0; x < this.width; x++)
+ {
+ var div_button = div_row.appendChild(document.createElement("div"));
+ div_button.className = "grid-tile";
+ var button = document.createElement("button");
+ button.className = "grid-button";
+ div_button.appendChild(button);
+ var c = "rgb(" + Math.round(255 * x / this.width) + ", " + Math.round(255 * y / this.height) + ", " + Math.round(255 * ( 1.0 - x / this.width))+")";
+ row.push(new Tile(x,y,this.tile_width,this.tile_height,button,c,div_button));
+ row[x].register_click_callback((i,j) => this.click_listener(i,j));
+ }
+ this.cells.push(row);
+ }
+ }
+
+ activate_all()
+ {
+ var x,y;
+ for (y = 0; y < this.height; y++)
+ {
+ for (x = 0; x < this.width; x++)
+ {
+ this.cells[y][x].activate();
+ }
+ }
+ }
+
+ deactivate_all()
+ {
+ var x,y;
+ for (y = 0; y < this.height; y++)
+ {
+ for (x = 0; x < this.width; x++)
+ {
+ this.cells[y][x].deactivate();
+ }
+ }
+ }
+
+ register_click_callback(func)
+ {
+ this.click_callback = func;
+ }
+
+ click_listener(x,y)
+ {
+ this.click_callback(x,y);
+ }
+
+ block()
+ {
+ var x,y;
+ for (y = 0; y < this.height; y++)
+ {
+ for (x = 0; x < this.width; x++)
+ {
+ this.cells[y][x].lock();
+ }
+ }
+ }
+
+ unblock()
+ {
+ var x,y;
+ for (y = 0; y < this.height; y++)
+ {
+ for (x = 0; x < this.width; x++)
+ {
+ this.cells[y][x].unlock();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..4891182
--- /dev/null
+++ b/index.html
@@ -0,0 +1,22 @@
+
+
+
+ Simon Says
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..69dad23
--- /dev/null
+++ b/main.js
@@ -0,0 +1,10 @@
+
+
+var width = getComputedStyle(document.body).getPropertyValue("--grid-width");
+var height = getComputedStyle(document.body).getPropertyValue("--grid-height");
+var tilesize = parseInt(getComputedStyle(document.body).getPropertyValue("--tile-size"));
+var default_opacity = getComputedStyle(document.body).getPropertyValue("--opacity")
+
+var grid = new Grid(width,height, document.getElementById("grid-container"), tilesize, tilesize);
+var game_manager = new GameManager(grid, document.getElementById("sidebar-container"), default_opacity);
+var sidebar = new Sidebar(grid, document.getElementById("control-container"), document.getElementById("info-container"), game_manager);
\ No newline at end of file
diff --git a/sidebar.js b/sidebar.js
new file mode 100644
index 0000000..5016a65
--- /dev/null
+++ b/sidebar.js
@@ -0,0 +1,53 @@
+class Sidebar
+{
+ constructor(grid, control_container, info_container, game_manager)
+ {
+ this.grid = grid;
+ this.control_container = control_container;
+ this.info_container = info_container
+ this.game_manager = game_manager;
+
+ this.build_controls();
+ this.game_manager.register_level_callback(c => this.level_callback_listener(c));
+ this.game_manager.register_statustext_callback(c => this.status_callback_listener(c));
+ this.bind_events();
+ }
+
+ build_controls()
+ {
+ this.b_start = document.createElement("button");
+ this.b_start.className = "control-button";
+ this.b_start.appendChild(document.createTextNode("Start"))
+
+ this.control_container.appendChild(this.b_start);
+
+ this.b_score = document.createElement("button");
+ this.b_score.className = "info-button";
+ this.t_scoretext = document.createTextNode("Round: ");
+ this.b_score.appendChild(this.t_scoretext);
+
+ this.b_status = document.createElement("button");
+ this.b_status.className = "status-button";
+ this.t_statustext = document.createTextNode("");
+ this.b_status.appendChild(this.t_statustext);
+
+ this.control_container.appendChild(this.b_start);
+ this.info_container.appendChild(this.b_score);
+ this.info_container.appendChild(this.b_status);
+ }
+
+ bind_events()
+ {
+ this.b_start.addEventListener("click", c => this.game_manager.restart(c));
+ }
+
+ level_callback_listener(level)
+ {
+ this.t_scoretext.nodeValue = "Round: " + level;
+ }
+
+ status_callback_listener(text)
+ {
+ this.t_statustext.nodeValue = text;
+ }
+}
\ No newline at end of file
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..9f1af71
--- /dev/null
+++ b/style.css
@@ -0,0 +1,131 @@
+canvas { background: #eee; width: 100%; height: 100%; }
+body {
+ margin: 0;
+ background: rgb(49, 51, 54);
+ padding: 100;
+ outline: 0;
+}
+
+:root{
+ --grid-width: 6;
+ --grid-height: 6;
+ --tile-size: 100px;
+ --button-margin: 5px;
+ --opacity: 0.164;
+}
+
+.grid-button {
+ border-radius: var(--button-margin);
+ margin: var(--button-margin);
+ width: calc(var(--tile-size) - (var(--button-margin) * 2));
+ height: calc(var(--tile-size) - (var(--button-margin) * 2));
+ display: inline-block;
+ outline: 0;
+ transition-duration: 0.3s;
+}
+
+.grid-button:focus {
+ outline: 0!important;
+}
+
+.control-button {
+ border-radius: 10px;
+ margin: 10px;
+ border: none;
+ display: list-item;
+ outline: 0;
+ transition-duration: 0.3s;
+ background: rgba(0, 0, 0, var(--opacity));
+ color: rgb(255, 255, 255);
+ font-size: 30pt;
+ width: 256px;
+}
+
+.control-button:hover {
+ background: rgba(255, 255, 255, var(--opacity));
+}
+
+.control-button:active {
+ background: rgba(255, 255, 255, calc(var(--opacity) * 2));
+}
+
+.info-button {
+ border-radius: 10px;
+ margin: 10px;
+ border: none;
+ outline: 0;
+ transition-duration: 0.3s;
+ display: flex;
+ flex-direction: column;
+ background: none;
+ color: rgb(255, 255, 255);
+ font-size: 30pt;
+ width: 256px;
+}
+
+.status-button {
+ border-radius: 10px;
+ margin: 10px;
+ border: none;
+ outline: 0;
+ transition-duration: 0.3s;
+ display: flex;
+ flex-direction: column;
+ background: none;
+ color: rgb(128, 128, 128);
+ font-size: 20pt;
+ width: 256px;
+}
+
+.grid-tile {
+ position: absolute;
+ width: var(--tile-size);
+ height: var(--tile-size);
+ float: center;
+ background: none;
+}
+
+.main-container {
+ text-align: center;
+}
+
+.grid-container {
+ border-radius: 10px;
+ padding: 10px;
+ margin: auto;
+ width: calc(var(--grid-width) * var(--tile-size));
+ height: calc(var(--grid-height) * var(--tile-size));
+ background: rgba(0, 0, 0, var(--opacity));
+ display: inline-block;
+ transition-duration: 0.3s;
+}
+
+.sidebar-container {
+ border-radius: 10px;
+ padding: 10px;
+ vertical-align: top;
+ background: rgba(0, 0, 0, var(--opacity));
+ display: inline-flex;
+ justify-content: space-between;
+ flex-direction:column;
+ min-height: calc(var(--grid-height) * var(--tile-size));
+ position: relative;
+ transition-duration: 1s;
+}
+
+.control-container {
+ border-radius: 10px;
+ padding: 10px;
+ top: 0;
+ background: none;
+}
+
+.info-container {
+ border-radius: 10px;
+ padding: 10px;
+ bottom: 0;
+ display: inline-block;
+ vertical-align: middle;
+ text-align: center;
+ background: none;
+}
\ No newline at end of file
diff --git a/tile.js b/tile.js
new file mode 100644
index 0000000..54f7dcf
--- /dev/null
+++ b/tile.js
@@ -0,0 +1,98 @@
+class Tile
+{
+ constructor(x,y,w,h, elem, color, div)
+ {
+ this.x = x;
+ this.y = y;
+
+ this.w = w;
+ this.h = h;
+
+ // reference to element
+ this.elem = elem;
+ this.color = color;
+ this.div = div;
+
+ this.locked = false;
+
+ this.div.style.transform = "translate( " + (x * w) + "px, " + (y*h) + "px )";
+ this.reset();
+ this.bind();
+
+ }
+
+ register_click_callback(func)
+ {
+ this.click_callback = func;
+ }
+
+ bind()
+ {
+ this.elem.addEventListener("mouseenter", c => this.on_mouseenter(c));
+ this.elem.addEventListener("mouseleave", c => this.on_mouseleave(c));
+ this.elem.addEventListener("click", c => this.on_click(c));
+ }
+
+ reset()
+ {
+ this.elem.style.backgroundColor = this.color;
+ this.elem.style.opacity = 1;
+ this.deactivate();
+ }
+
+ lock()
+ {
+ this.locked = true;
+ }
+
+ unlock()
+ {
+ this.locked = false;
+ }
+
+ deactivate()
+ {
+ this.is_activated = false;
+ this.elem.style.opacity = 0.3;
+ this.elem.style.border = "none";
+ }
+
+ activate()
+ {
+ this.is_activated = true;
+ this.elem.style.opacity = 1.0;
+ this.elem.style.border = "2px solid rgba(255,255,255,0.3)";
+ }
+
+ on_mouseenter()
+ {
+ if (!this.is_activated && !this.locked)
+ {
+ this.elem.style.opacity = 0.7;
+ this.elem.style.border = "1px solid rgba(255,255,255,0.3)";
+ }
+ }
+
+ on_mouseleave()
+ {
+ if (!this.is_activated && !this.locked)
+ {
+ this.elem.style.opacity = 0.3;
+ this.elem.style.border = "none";
+ }
+ }
+
+ on_click()
+ {
+ if (!this.locked)
+ {
+ var active_before = this.is_activated;
+ this.activate();
+ if (!active_before)
+ {
+ this.click_callback(this.x, this.y);
+ }
+ }
+ }
+
+}
\ No newline at end of file