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