"use strict"; var level_number = 1; var is_custom_level = false; var unlocked_level_number = 1; var canvas = document.getElementById("canvas"); var sidebar_tray = document.getElementById("sidebar"); var sidebar_button = document.getElementById("hamburger"); var retry_button = document.getElementById("retryButton"); var reset_button = document.getElementById("resetButton"); var tile_set_div = document.getElementById("tileSetDiv"); var tile_set_select = document.getElementById("tileSetSelect"); var level_number_span = document.getElementById("level_number_span"); var level_down_button = document.getElementById("level_down_button"); var level_up_button = document.getElementById("level_up_button"); var show_custom_level_button = document.getElementById("show_custom_level_button"); var level_settings_div = document.getElementById("level_settings_div"); var custom_colors_select = document.getElementById("custom_colors_select"); var custom_colors_div = document.getElementById("custom_colors_div"); var custom_color_two_overlap_option = document.getElementById("custom_color_two_overlap_option"); var custom_shape_select = document.getElementById("custom_shape_select"); var custom_shape_div = document.getElementById("custom_shape_div"); var custom_toroidal_checkbox = document.getElementById("custom_toroidal_checkbox"); var custom_toroidal_div = document.getElementById("custom_toroidal_div"); var custom_rough_checkbox = document.getElementById("custom_rough_checkbox"); var custom_rough_div = document.getElementById("custom_rough_div"); var rough_label_span = document.getElementById("rough_label_span"); var custom_cement_mode_checkbox = document.getElementById("custom_cement_mode_checkbox"); var custom_cement_mode_div = document.getElementById("custom_cement_mode_div"); var custom_width_spinner = document.getElementById("custom_width_spinner"); var custom_height_spinner = document.getElementById("custom_height_spinner"); var pi = Math.PI; var sqrt3 = Math.sqrt(3); var global_alpha = 1.0; var buffer_canvas = document.createElement("canvas"); var asdf_alpha = 1.0; var line_width_multiplier = 1.0; var line_width_multiplier_factor = 0.4; var asdf_background_canvas = document.createElement("canvas"); var tile_canvas = document.createElement("canvas"); var asdf_foreground_canvas = document.createElement("canvas"); var all_canvases = [ canvas, buffer_canvas, asdf_background_canvas, tile_canvas, asdf_foreground_canvas, ]; var render_target_canvases = [ asdf_background_canvas, tile_canvas, asdf_foreground_canvas, ]; var GameState; (function (GameState) { GameState[GameState["Playing"] = 0] = "Playing"; GameState[GameState["FadeOut"] = 1] = "FadeOut"; GameState[GameState["FadeToRoses"] = 2] = "FadeToRoses"; GameState[GameState["SmellTheRoses"] = 3] = "SmellTheRoses"; GameState[GameState["FadeIn"] = 4] = "FadeIn"; })(GameState || (GameState = {})); var game_state = GameState.Playing; var TileSet; (function (TileSet) { TileSet[TileSet["Trypo"] = 0] = "Trypo"; TileSet[TileSet["Ribbon"] = 1] = "Ribbon"; TileSet[TileSet["Iso"] = 2] = "Iso"; TileSet[TileSet["Chaos"] = 3] = "Chaos"; })(TileSet || (TileSet = {})); var tile_set = TileSet.Trypo; var Shape; (function (Shape) { Shape[Shape["Square"] = 0] = "Square"; Shape[Shape["Hexagon"] = 1] = "Hexagon"; })(Shape || (Shape = {})); var ColorRules; (function (ColorRules) { ColorRules[ColorRules["Single"] = 0] = "Single"; ColorRules[ColorRules["TwoSeparate"] = 1] = "TwoSeparate"; ColorRules[ColorRules["TwoOverlap"] = 2] = "TwoOverlap"; })(ColorRules || (ColorRules = {})); var EndpointStyle; (function (EndpointStyle) { EndpointStyle[EndpointStyle["LargeRing"] = 0] = "LargeRing"; EndpointStyle[EndpointStyle["SmallRing"] = 1] = "SmallRing"; EndpointStyle[EndpointStyle["LargeDot"] = 2] = "LargeDot"; })(EndpointStyle || (EndpointStyle = {})); ; var Level = (function () { function Level(parameters) { this.tiles_per_row = parameters.size[0]; this.tiles_per_column = parameters.size[1]; this.shape = parameters.shape; this.cement_mode = parameters.cement_mode || false; this.toroidal = parameters.toroidal || false; this.rough = parameters.rough || false; this.perfect_so_far = false; this.colors = parameters.colors; switch (parameters.colors) { case ColorRules.Single: this.color_count = 1; this.allow_overlap = false; break; case ColorRules.TwoSeparate: this.color_count = 2; this.allow_overlap = false; break; case ColorRules.TwoOverlap: this.color_count = 2; this.allow_overlap = true; break; default: throw new AssertionFailure(); } this.tiles = []; this.frozen_tiles = {}; this.recent_touch_queue = []; for (var i = 0; i < this.tiles_per_row * this.tiles_per_column; i++) { var colors = []; for (var color_index = 0; color_index < this.color_count; color_index++) { colors.push(0); } this.tiles.push(colors); } switch (this.shape) { case Shape.Square: this.units_per_tile_x = 1; this.units_per_tile_y = 1; this.tile_animation_time = 150; this.edges_per_tile = 4; this.display_offset_x = 0; this.display_offset_y = 0; if (this.toroidal) { this.display_tiles_x = this.tiles_per_row + 3; this.display_tiles_y = this.tiles_per_column + 3; } else { this.display_tiles_x = this.tiles_per_row - 1; this.display_tiles_y = this.tiles_per_column - 1; } break; case Shape.Hexagon: this.units_per_tile_x = 1.5; this.units_per_tile_y = sqrt3; this.tile_animation_time = 120; this.edges_per_tile = 6; if (this.toroidal) { this.display_offset_x = -0.5; this.display_offset_y = 0; this.display_tiles_x = this.tiles_per_row + 3; this.display_tiles_y = this.tiles_per_column + 3; } else { this.display_offset_x = 0.25; this.display_offset_y = sqrt3 / 4; this.display_tiles_x = this.tiles_per_row - 1; this.display_tiles_y = this.tiles_per_column - 0.5; } break; default: throw new AssertionFailure(); } assert(!(this.shape == Shape.Hexagon && (this.tiles_per_row & 1) && this.toroidal)); } Level.prototype.initializePossibilityForPerfect = function () { this.original_tiles = JSON.parse(JSON.stringify(this.tiles)); this.perfect_so_far = true; }; Level.prototype.getTileIndexFromDisplayPoint = function (display_x, display_y) { switch (this.shape) { case Shape.Square: { return this.getTileIndexFromCoord(Math.floor(display_x), Math.floor(display_y)); } case Shape.Hexagon: { var general_neighborhood_x = Math.floor(display_x / 1.5); var general_neighborhood_y = Math.floor(display_y / sqrt3); var closest_distance_squared = Infinity; var closest_tile = NaN; for (var _i = 0, _a = [general_neighborhood_x - 1, general_neighborhood_x, general_neighborhood_x + 1]; _i < _a.length; _i++) { var x = _a[_i]; for (var _b = 0, _c = [general_neighborhood_y - 1, general_neighborhood_y, general_neighborhood_y + 1]; _b < _c.length; _b++) { var y = _c[_b]; var center_x = 1.5 * x + 1; var center_y = sqrt3 * (y + ((x & 1) ? 1 : 0.5)); var distance_squared = Math.pow((display_y - center_y), 2) + Math.pow((display_x - center_x), 2); if (distance_squared < closest_distance_squared) { closest_distance_squared = distance_squared; closest_tile = this.getTileIndexFromCoord(euclideanMod(x, this.tiles_per_row), euclideanMod(y, this.tiles_per_column)); } } } assert(!isNaN(closest_tile)); return closest_tile; } default: throw new AssertionFailure(); } }; Level.prototype.getTileIndexFromCoord = function (x, y) { return y * this.tiles_per_row + x; }; Level.prototype.getTileCoordFromIndex = function (location) { var x = location % this.tiles_per_row; var y = (location - x) / this.tiles_per_row; return { x: x, y: y }; }; Level.prototype.allTileIndexes = function () { var result = []; for (var i = 0; i < this.tiles.length; i++) { result.push(i); } return result; }; Level.prototype.allEdges = function () { switch (this.shape) { case Shape.Square: { var result = []; for (var y = 0; y < this.tiles_per_column; y++) { for (var x = 0; x < this.tiles_per_row; x++) { result.push({ tile_index: this.getTileIndexFromCoord(x, y), direction: 1 }); result.push({ tile_index: this.getTileIndexFromCoord(x, y), direction: 2 }); } } return result; } case Shape.Hexagon: { var result = []; for (var y = 0; y < this.tiles_per_column; y++) { for (var x = 0; x < this.tiles_per_row; x++) { var tile_index = this.getTileIndexFromCoord(x, y); result.push({ tile_index: tile_index, direction: 1 }); result.push({ tile_index: tile_index, direction: 2 }); result.push({ tile_index: tile_index, direction: 4 }); } } var debug_no_dups = {}; for (var _i = 0, result_1 = result; _i < result_1.length; _i++) { var vector = result_1[_i]; var hash = "" + vector.tile_index + "," + vector.direction; if (debug_no_dups[hash]) throw new AssertionFailure(); debug_no_dups[hash] = true; } return result; } default: throw new AssertionFailure(); } }; Level.prototype.getTileIndexFromVector = function (tile_index, direction) { switch (this.shape) { case Shape.Square: { var _a = this.getTileCoordFromIndex(tile_index), x = _a.x, y = _a.y; switch (direction) { case 1: x = euclideanMod(x + 1, this.tiles_per_row); break; case 2: y = euclideanMod(y + 1, this.tiles_per_column); break; case 4: x = euclideanMod(x - 1, this.tiles_per_row); break; case 8: y = euclideanMod(y - 1, this.tiles_per_column); break; default: throw new AssertionFailure(); } return this.getTileIndexFromCoord(x, y); } case Shape.Hexagon: { var _b = this.getTileCoordFromIndex(tile_index), x = _b.x, y = _b.y; var is_offset_down = !!(x & 1); switch (direction) { case 1: x = euclideanMod(x + 1, this.tiles_per_row); if (is_offset_down) y = euclideanMod(y + 1, this.tiles_per_column); break; case 2: y = euclideanMod(y + 1, this.tiles_per_column); break; case 4: x = euclideanMod(x - 1, this.tiles_per_row); if (is_offset_down) y = euclideanMod(y + 1, this.tiles_per_column); break; case 8: x = euclideanMod(x - 1, this.tiles_per_row); if (!is_offset_down) y = euclideanMod(y - 1, this.tiles_per_column); break; case 16: y = euclideanMod(y - 1, this.tiles_per_column); break; case 32: x = euclideanMod(x + 1, this.tiles_per_row); if (!is_offset_down) y = euclideanMod(y - 1, this.tiles_per_column); break; default: throw new AssertionFailure(); } return this.getTileIndexFromCoord(x, y); } default: throw new AssertionFailure(); } }; Level.prototype.reverseDirection = function (direction) { var times = this.edges_per_tile / 2; return this.rotateValue(direction, times); }; Level.prototype.rotateValue = function (value, times) { times = euclideanMod(times, this.edges_per_tile); var mask = (1 << this.edges_per_tile) - 1; return mask & ((value << times) | (value >> (this.edges_per_tile - times))); }; Level.prototype.rotateTile = function (tile_index, times) { var all_back_to_original_values = true; for (var color_index = 0; color_index < this.color_count; color_index++) { var color_value = this.tiles[tile_index][color_index]; color_value = this.rotateValue(color_value, times); this.tiles[tile_index][color_index] = color_value; if (this.perfect_so_far && this.original_tiles != null) { if (this.original_tiles[tile_index][color_index] !== color_value) { all_back_to_original_values = false; } } } if (all_back_to_original_values) { this.perfect_so_far = false; } }; Level.prototype.rotateRandomly = function (tile_index) { this.rotateTile(tile_index, Math.floor(Math.random() * this.edges_per_tile)); }; Level.prototype.renderGridLines = function (context) { switch (this.shape) { case Shape.Square: { if (this.toroidal) { for (var x = -this.tiles_per_row; x < 2 * this.tiles_per_row; x++) { context.moveTo(x, -this.tiles_per_column); context.lineTo(x, 2 * this.tiles_per_column); } for (var y = -this.tiles_per_column; y < 2 * this.tiles_per_column; y++) { context.moveTo(-this.tiles_per_row, y); context.lineTo(2 * this.tiles_per_row, y); } } else { for (var x = 2; x < this.tiles_per_row - 1; x++) { context.moveTo(x, 1); context.lineTo(x, this.tiles_per_column - 1); } for (var y = 2; y < this.tiles_per_column - 1; y++) { context.moveTo(1, y); context.lineTo(this.tiles_per_row - 1, y); } } break; } case Shape.Hexagon: { if (this.toroidal) { var top = -this.tiles_per_column; var bottom = 2 * this.tiles_per_column; var left = -(this.tiles_per_row & ~1); var right = 2 * this.tiles_per_row; } else { top = 0; bottom = this.tiles_per_column; left = 0; right = this.tiles_per_row; } for (var y = top; y <= bottom; y++) { var high_y = sqrt3 * y; var mid_y = sqrt3 * (y + 0.5); var low_y = sqrt3 * (y + 1); for (var x = left; x <= right; x += 2) { var left_x = 1.5 * x; context.moveTo(left_x + 0.5, low_y); context.lineTo(left_x + 0.0, mid_y); context.lineTo(left_x + 0.5, high_y); context.lineTo(left_x + 1.5, high_y); context.lineTo(left_x + 2.0, mid_y); context.lineTo(left_x + 1.5, low_y); context.moveTo(left_x + 2.0, mid_y); context.lineTo(left_x + 3.0, mid_y); } } break; } default: throw new AssertionFailure(); } }; Level.prototype.renderTile = function (context, color_value, x, y, animation_progress, endpoint_style, tile_set) { switch (this.shape) { case Shape.Square: { if (color_value === 0) return; context.save(); try { context.translate(x + 0.5, y + 0.5); if (animation_progress !== 0) { context.rotate(pi / 2 * animation_progress); } switch (color_value) { case 1: break; case 2: color_value = 1; context.rotate(pi / 2); break; case 3: break; case 4: color_value = 1; context.rotate(pi); break; case 5: break; case 6: color_value = 3; context.rotate(pi / 2); break; case 7: break; case 8: color_value = 1; context.rotate(pi * 1.5); break; case 9: color_value = 3; context.rotate(pi * 1.5); break; case 10: color_value = 5; context.rotate(pi / 2); break; case 11: color_value = 7; context.rotate(pi * 1.5); break; case 12: color_value = 3; context.rotate(pi); break; case 13: color_value = 7; context.rotate(pi); break; case 14: color_value = 7; context.rotate(pi / 2); break; case 15: break; default: throw new AssertionFailure(); } switch (color_value) { case 1: switch (endpoint_style) { case EndpointStyle.LargeRing: context.beginPath(); context.arc(0, 0, 0.25, 0, 2 * pi); context.lineTo(0.5, 0); context.stroke(); break; case EndpointStyle.SmallRing: context.beginPath(); context.arc(0, 0, 0.15, 0, 2 * pi); context.lineTo(0.5, 0); context.stroke(); break; case EndpointStyle.LargeDot: context.beginPath(); context.arc(0, 0, line_width_multiplier * 0.25, 0, 2 * pi); context.fill(); context.beginPath(); context.moveTo(0, 0); context.lineTo(0.5, 0); context.stroke(); break; default: throw new AssertionFailure(); } break; case 3: context.beginPath(); context.arc(0.5, 0.5, 0.5, pi, pi * 1.5); context.stroke(); break; case 5: context.beginPath(); context.moveTo(0.5, 0); context.lineTo(-0.5, 0); context.stroke(); break; case 7: context.beginPath(); context.arc(-0.5, 0.5, 0.5, pi * 1.5, 2 * pi); context.stroke(); context.beginPath(); context.arc(0.5, 0.5, 0.5, pi, pi * 1.5); context.stroke(); break; case 15: context.beginPath(); context.arc(0.5, 0.5, 0.5, pi, pi * 1.5); context.stroke(); context.beginPath(); context.arc(0.5, -0.5, 0.5, pi / 2, pi); context.stroke(); context.beginPath(); context.arc(-0.5, -0.5, 0.5, 0, pi / 2); context.stroke(); context.beginPath(); context.arc(-0.5, 0.5, 0.5, pi * 1.5, 2 * pi); context.stroke(); break; default: throw new AssertionFailure(); } } finally { context.restore(); } break; } case Shape.Hexagon: { if (color_value === 0) return; context.save(); try { if (x & 1) { context.translate(1.5 * x + 1, sqrt3 * (y + 1.0)); } else { context.translate(1.5 * x + 1, sqrt3 * (y + 0.5)); } if (animation_progress !== 0) { context.rotate(pi / 3 * animation_progress); } if (tile_set === TileSet.Iso) { for (var i = 0; i < 6; i++) { if (color_value & (1 << i)) { context.beginPath(); context.moveTo(0, 0); context.lineTo(0.75, sqrt3 / 4); context.stroke(); } context.rotate(pi / 3); } break; } switch (color_value) { case 1: break; case 2: color_value = 1; context.rotate(1 / 3 * pi); break; case 4: color_value = 1; context.rotate(2 / 3 * pi); break; case 8: color_value = 1; context.rotate(pi); break; case 16: color_value = 1; context.rotate(4 / 3 * pi); break; case 32: color_value = 1; context.rotate(5 / 3 * pi); break; case 3: break; case 6: color_value = 3; context.rotate(1 / 3 * pi); break; case 12: color_value = 3; context.rotate(2 / 3 * pi); break; case 24: color_value = 3; context.rotate(pi); break; case 48: color_value = 3; context.rotate(4 / 3 * pi); break; case 33: color_value = 3; context.rotate(5 / 3 * pi); break; case 5: break; case 10: color_value = 5; context.rotate(1 / 3 * pi); break; case 20: color_value = 5; context.rotate(2 / 3 * pi); break; case 40: color_value = 5; context.rotate(pi); break; case 17: color_value = 5; context.rotate(4 / 3 * pi); break; case 34: color_value = 5; context.rotate(5 / 3 * pi); break; case 7: break; case 14: color_value = 7; context.rotate(1 / 3 * pi); break; case 28: color_value = 7; context.rotate(2 / 3 * pi); break; case 56: color_value = 7; context.rotate(pi); break; case 49: color_value = 7; context.rotate(4 / 3 * pi); break; case 35: color_value = 7; context.rotate(5 / 3 * pi); break; case 9: break; case 18: color_value = 9; context.rotate(1 / 3 * pi); break; case 36: color_value = 9; context.rotate(2 / 3 * pi); break; case 11: break; case 22: color_value = 11; context.rotate(1 / 3 * pi); break; case 44: color_value = 11; context.rotate(2 / 3 * pi); break; case 25: color_value = 11; context.rotate(pi); break; case 50: color_value = 11; context.rotate(4 / 3 * pi); break; case 37: color_value = 11; context.rotate(5 / 3 * pi); break; case 13: break; case 26: color_value = 13; context.rotate(1 / 3 * pi); break; case 52: color_value = 13; context.rotate(2 / 3 * pi); break; case 41: color_value = 13; context.rotate(pi); break; case 19: color_value = 13; context.rotate(4 / 3 * pi); break; case 38: color_value = 13; context.rotate(5 / 3 * pi); break; case 15: break; case 30: color_value = 15; context.rotate(1 / 3 * pi); break; case 60: color_value = 15; context.rotate(2 / 3 * pi); break; case 57: color_value = 15; context.rotate(pi); break; case 51: color_value = 15; context.rotate(4 / 3 * pi); break; case 39: color_value = 15; context.rotate(5 / 3 * pi); break; case 21: break; case 42: color_value = 21; context.rotate(1 / 3 * pi); break; case 23: break; case 46: color_value = 23; context.rotate(1 / 3 * pi); break; case 29: color_value = 23; context.rotate(2 / 3 * pi); break; case 58: color_value = 23; context.rotate(pi); break; case 53: color_value = 23; context.rotate(4 / 3 * pi); break; case 43: color_value = 23; context.rotate(5 / 3 * pi); break; case 27: break; case 54: color_value = 27; context.rotate(1 / 3 * pi); break; case 45: color_value = 27; context.rotate(2 / 3 * pi); break; case 31: break; case 62: color_value = 31; context.rotate(1 / 3 * pi); break; case 61: color_value = 31; context.rotate(2 / 3 * pi); break; case 59: color_value = 31; context.rotate(pi); break; case 55: color_value = 31; context.rotate(4 / 3 * pi); break; case 47: color_value = 31; context.rotate(5 / 3 * pi); break; case 63: break; default: throw new AssertionFailure(); } switch (color_value) { case 1: switch (tile_set) { case TileSet.Ribbon: case TileSet.Trypo: context.rotate(pi / 6); switch (endpoint_style) { case EndpointStyle.LargeRing: context.beginPath(); context.arc(0, 0, 0.5, 0, 2 * pi); context.lineTo(sqrt3 / 2, 0); context.stroke(); break; case EndpointStyle.SmallRing: context.beginPath(); context.arc(0, 0, 0.33, 0, 2 * pi); context.lineTo(sqrt3 / 2, 0); context.stroke(); break; case EndpointStyle.LargeDot: context.beginPath(); context.arc(0, 0, 0.5, 0, 2 * pi); context.fill(); context.beginPath(); context.moveTo(0, 0); context.lineTo(sqrt3 / 2, 0); context.stroke(); break; default: throw new AssertionFailure(); } break; } break; case 3: switch (tile_set) { case TileSet.Ribbon: case TileSet.Trypo: context.beginPath(); context.arc(0.5, sqrt3 / 2, 0.5, pi, 5 / 3 * pi); context.stroke(); break; } break; case 5: switch (tile_set) { case TileSet.Ribbon: case TileSet.Trypo: context.beginPath(); context.arc(0, sqrt3, 1.5, 4 / 3 * pi, 5 / 3 * pi); context.stroke(); break; } break; case 7: switch (tile_set) { case TileSet.Ribbon: case TileSet.Trypo: context.beginPath(); context.arc(-0.5, sqrt3 / 2, 0.5, 4 / 3 * pi, 2 * pi); context.arc(0.5, sqrt3 / 2, 0.5, pi, 5 / 3 * pi); context.stroke(); break; } break; case 9: switch (tile_set) { case TileSet.Ribbon: case TileSet.Trypo: context.beginPath(); context.moveTo(0.75, sqrt3 / 4); context.lineTo(-0.75, -sqrt3 / 4); context.stroke(); break; } break; case 11: switch (tile_set) { case TileSet.Ribbon: context.beginPath(); context.arc(0.5, sqrt3 / 2, 0.5, pi, 5 / 3 * pi); context.lineTo(-0.75, -sqrt3 / 4); context.stroke(); break; case TileSet.Trypo: context.beginPath(); context.arc(-1.5, sqrt3 / 2, 1.5, 5 / 3 * pi, 2 * pi); context.arc(0.5, sqrt3 / 2, 0.5, pi, 5 / 3 * pi); context.stroke(); break; } break; case 13: switch (tile_set) { case TileSet.Ribbon: context.beginPath(); context.moveTo(0.75, sqrt3 / 4); context.arc(-1, 0, 0.5, 5 / 3 * pi, 1 / 3 * pi); context.stroke(); break; case TileSet.Trypo: context.beginPath(); context.arc(-1, 0, 0.5, 5 / 3 * pi, 1 / 3 * pi); context.arc(0, sqrt3, 1.5, 4 / 3 * pi, 5 / 3 * pi); context.stroke(); break; } break; case 15: switch (tile_set) { case TileSet.Ribbon: context.beginPath(); context.arc(0, sqrt3, 1.5, 4 / 3 * pi, 5 / 3 * pi); context.stroke(); context.rotate(pi / 3); context.beginPath(); context.arc(0, sqrt3, 1.5, 4 / 3 * pi, 5 / 3 * pi); context.stroke(); break; case TileSet.Trypo: context.beginPath(); context.arc(-1, 0, 0.5, 5 / 3 * pi, 1 / 3 * pi); context.arc(-0.5, sqrt3 / 2, 0.5, 4 / 3 * pi, 2 * pi); context.arc(0.5, sqrt3 / 2, 0.5, pi, 5 / 3 * pi); context.stroke(); break; } break; case 21: switch (tile_set) { case TileSet.Ribbon: case TileSet.Trypo: context.beginPath(); context.arc(1.5, -sqrt3 / 2, 1.5, 2 / 3 * pi, pi); context.arc(-1.5, -sqrt3 / 2, 1.5, 0, 1 / 3 * pi); context.arc(0, sqrt3, 1.5, 4 / 3 * pi, 5 / 3 * pi); context.stroke(); break; } break; case 23: switch (tile_set) { case TileSet.Ribbon: context.beginPath(); context.arc(0, sqrt3, 1.5, 4 / 3 * pi, 5 / 3 * pi); context.stroke(); context.beginPath(); context.moveTo(0, sqrt3 / 2); context.lineTo(0, -sqrt3 / 2); context.stroke(); break; case TileSet.Trypo: context.beginPath(); context.arc(1.5, -sqrt3 / 2, 1.5, 2 / 3 * pi, pi); context.arc(-1.5, -sqrt3 / 2, 1.5, 0, 1 / 3 * pi); context.arc(-0.5, sqrt3 / 2, 0.5, 4 / 3 * pi, 2 * pi); context.arc(0.5, sqrt3 / 2, 0.5, pi, 5 / 3 * pi); context.stroke(); break; } break; case 27: switch (tile_set) { case TileSet.Ribbon: context.beginPath(); context.moveTo(0.75, sqrt3 / 4); context.lineTo(-0.75, -sqrt3 / 4); context.stroke(); context.beginPath(); context.moveTo(0, sqrt3 / 2); context.lineTo(0, -sqrt3 / 2); context.stroke(); break; case TileSet.Trypo: context.beginPath(); context.arc(-0.5, -sqrt3 / 2, 0.5, 0, 2 / 3 * pi); context.stroke(); context.beginPath(); context.arc(0.5, sqrt3 / 2, 0.5, pi, 5 / 3 * pi); context.stroke(); break; } break; case 31: switch (tile_set) { case TileSet.Ribbon: context.beginPath(); context.arc(0, sqrt3, 1.5, 4 / 3 * pi, 5 / 3 * pi); context.stroke(); context.rotate(pi / 3); context.beginPath(); context.arc(0.5, sqrt3 / 2, 0.5, pi, 5 / 3 * pi); context.stroke(); context.rotate(pi / 3); context.beginPath(); context.arc(0.5, sqrt3 / 2, 0.5, pi, 5 / 3 * pi); context.stroke(); context.beginPath(); context.arc(0, sqrt3, 1.5, 4 / 3 * pi, 5 / 3 * pi); context.stroke(); break; case TileSet.Trypo: context.beginPath(); context.arc(-0.5, -sqrt3 / 2, 0.5, 0, 2 / 3 * pi); context.arc(-1, 0, 0.5, 5 / 3 * pi, 7 / 3 * pi); context.arc(-0.5, sqrt3 / 2, 0.5, 4 / 3 * pi, 2 * pi); context.arc(0.5, sqrt3 / 2, 0.5, pi, 5 / 3 * pi); context.stroke(); break; } break; case 63: switch (tile_set) { case TileSet.Ribbon: context.beginPath(); context.moveTo(0.75, sqrt3 / 4); context.lineTo(-0.75, -sqrt3 / 4); context.stroke(); context.beginPath(); context.moveTo(0, sqrt3 / 2); context.lineTo(0, -sqrt3 / 2); context.stroke(); context.beginPath(); context.moveTo(0.75, -sqrt3 / 4); context.lineTo(-0.75, sqrt3 / 4); context.stroke(); break; case TileSet.Trypo: context.beginPath(); context.arc(1, 0, 0.5, 2 / 3 * pi, 4 / 3 * pi); context.arc(0.5, -sqrt3 / 2, 0.5, 1 / 3 * pi, pi); context.arc(-0.5, -sqrt3 / 2, 0.5, 0, 2 / 3 * pi); context.arc(-1, 0, 0.5, 5 / 3 * pi, 1 / 3 * pi); context.arc(-0.5, sqrt3 / 2, 0.5, 4 / 3 * pi, 2 * pi); context.arc(0.5, sqrt3 / 2, 0.5, pi, 5 / 3 * pi); context.stroke(); break; } break; default: throw new AssertionFailure(); } } finally { context.restore(); } break; } default: throw new AssertionFailure(); } }; Level.prototype.renderTileBackground = function (context, x, y) { switch (this.shape) { case Shape.Square: { context.fillRect(x, y, 1, 1); break; } case Shape.Hexagon: { context.save(); try { if (x & 1) { context.translate(1.5 * x + 1, sqrt3 * (y + 1.0)); } else { context.translate(1.5 * x + 1, sqrt3 * (y + 0.5)); } context.beginPath(); context.moveTo(-0.5, -sqrt3 / 2); context.lineTo(0.5, -sqrt3 / 2); context.lineTo(1, 0); context.lineTo(0.5, sqrt3 / 2); context.lineTo(-0.5, sqrt3 / 2); context.lineTo(-1, 0); context.lineTo(-0.5, -sqrt3 / 2); context.fill(); } finally { context.restore(); } break; } default: throw new AssertionFailure(); } }; Level.prototype.isInBounds = function (tile_index) { if (this.toroidal) return true; var _a = this.getTileCoordFromIndex(tile_index), x = _a.x, y = _a.y; return (1 <= x && x < this.tiles_per_row - 1 && 1 <= y && y < this.tiles_per_column - 1); }; Level.prototype.touchTile = function (tile_index) { if (!this.cement_mode) return; var index = this.recent_touch_queue.indexOf(tile_index); if (index !== -1) { this.recent_touch_queue.splice(index, 1); this.recent_touch_queue.unshift(tile_index); } else { this.recent_touch_queue.unshift(tile_index); if (this.recent_touch_queue.length > 3) { var freezing_tile = this.recent_touch_queue.pop(); this.frozen_tiles[freezing_tile] = true; } } }; Level.prototype.countUnsolved = function () { var result = 0; for (var _i = 0, _a = this.allEdges(); _i < _a.length; _i++) { var _b = _a[_i], tile_index = _b.tile_index, direction = _b.direction; for (var color_index = 0; color_index < this.color_count; color_index++) { var a = this.getEdgeValue(tile_index, color_index, direction); var b = this.getEdgeValue(this.getTileIndexFromVector(tile_index, direction), color_index, this.reverseDirection(direction)); if (a !== b) result += 1; } } return result; }; Level.prototype.getEdgeValue = function (tile_index, color_index, direction) { return +!!(this.tiles[tile_index][color_index] & direction); }; Level.prototype.renderLevel = function (context) { for (var color_index = 0; color_index < this.color_count; color_index++) { context.lineCap = "round"; context.lineJoin = "round"; var endpoint_style = void 0; if (this.color_count === 1) { context.strokeStyle = "#000"; context.lineWidth = line_width_multiplier * level.units_per_tile_x * 0.1; endpoint_style = EndpointStyle.LargeRing; } else if (this.color_count === 2 && !this.allow_overlap) { switch (color_index) { case 0: context.strokeStyle = "#99f"; context.lineWidth = line_width_multiplier * level.units_per_tile_x * 0.2; endpoint_style = EndpointStyle.LargeDot; context.fillStyle = context.strokeStyle; break; case 1: context.strokeStyle = "#c06"; context.lineWidth = line_width_multiplier * level.units_per_tile_x * 0.075; endpoint_style = EndpointStyle.SmallRing; break; default: throw new AssertionFailure(); } } else if (this.color_count === 2 && this.allow_overlap) { switch (color_index) { case 0: context.strokeStyle = "#e784e1"; context.lineWidth = line_width_multiplier * level.units_per_tile_x * 0.4; context.lineCap = "butt"; context.lineJoin = "miter"; endpoint_style = EndpointStyle.LargeDot; context.fillStyle = context.strokeStyle; break; case 1: context.strokeStyle = "#000caa"; context.lineWidth = line_width_multiplier * level.units_per_tile_x * 0.075; endpoint_style = EndpointStyle.LargeRing; break; default: throw new AssertionFailure(); } } else { throw new AssertionFailure(); } for (var _i = 0, _a = this.allTileIndexes(); _i < _a.length; _i++) { var location_1 = _a[_i]; var _b = this.getTileCoordFromIndex(location_1), x = _b.x, y = _b.y; var color_value = this.tiles[location_1][color_index]; var tile_rotation_animation = tile_rotation_animations[location_1]; var animation_progress = tile_rotation_animation ? tile_rotation_animation.rotation : 0; this.renderTiles(context, color_value, x, y, animation_progress, endpoint_style); } } }; Level.prototype.renderAsdfBackground = function (context) { if (game_state !== GameState.SmellTheRoses) { context.strokeStyle = "#ddd"; context.lineWidth = 0.03; context.lineCap = "round"; context.lineJoin = "round"; context.beginPath(); this.renderGridLines(context); context.stroke(); } if (this.cement_mode || this.rough) { for (var _i = 0, _a = this.allTileIndexes(); _i < _a.length; _i++) { var location_2 = _a[_i]; if (level.frozen_tiles[location_2]) { context.fillStyle = "#ccc"; } else { var age = level.recent_touch_queue.indexOf(location_2); if (age === -1) continue; switch (age) { case 0: context.fillStyle = "#eee"; break; case 1: case 2: context.fillStyle = "#eee"; break; default: throw new AssertionFailure(); } } var _b = this.getTileCoordFromIndex(location_2), x = _b.x, y = _b.y; this.renderTileBackgrounds(context, x, y); } } }; Level.prototype.renderAsdfForeground = function (context) { if (this.toroidal) { var left = this.display_offset_x; var right = left + this.tiles_per_row * this.units_per_tile_x; var top_1 = this.display_offset_y; var bottom = top_1 + this.tiles_per_column * this.units_per_tile_y; context.strokeStyle = "rgba(0,0,0,0.5)"; context.lineWidth = 0.05; context.lineCap = "round"; context.lineJoin = "round"; context.beginPath(); context.moveTo(left, top_1); context.lineTo(right, top_1); context.lineTo(right, bottom); context.lineTo(left, bottom); context.lineTo(left, top_1); context.stroke(); } }; Level.prototype.renderTiles = function (context, color_value, x, y, animation_progress, endpoint_style) { var _tile_set = tile_set; if (_tile_set === TileSet.Chaos) { _tile_set = [ TileSet.Trypo, TileSet.Ribbon, TileSet.Iso, ][(hashU32(x) ^ hashU32(y) ^ hashU32(level_number)) % 3]; } if (!this.toroidal) return this.renderTile(context, color_value, x, y, animation_progress, endpoint_style, _tile_set); for (var _i = 0, _a = [-1, 0, 1]; _i < _a.length; _i++) { var dy = _a[_i]; for (var _b = 0, _c = [-1, 0, 1]; _b < _c.length; _b++) { var dx = _c[_b]; this.renderTile(context, color_value, x + dx * this.tiles_per_row, y + dy * this.tiles_per_column, animation_progress, endpoint_style, _tile_set); } } }; Level.prototype.renderTileBackgrounds = function (context, x, y) { if (!this.toroidal) return this.renderTileBackground(context, x, y); for (var _i = 0, _a = [-1, 0, 1]; _i < _a.length; _i++) { var dy = _a[_i]; for (var _b = 0, _c = [-1, 0, 1]; _b < _c.length; _b++) { var dx = _c[_b]; this.renderTileBackground(context, x + dx * this.tiles_per_row, y + dy * this.tiles_per_column); } } }; return Level; }()); window.addEventListener("resize", function () { handleResize(); }); function isSidebarShowing() { return sidebar_button.classList.contains("active"); } function showSidebar() { sidebar_button.classList.add("active"); sidebar_tray.classList.add("active"); } function hideSidebar() { sidebar_button.classList.remove("active"); sidebar_tray.classList.remove("active"); } sidebar_button.addEventListener("mousedown", function (event) { if (event.altKey || event.ctrlKey || event.shiftKey) return; if (event.button !== 0) return; if (isSidebarShowing()) { hideSidebar(); } else { showSidebar(); } }); window.addEventListener("keydown", function (event) { if (event.altKey || event.ctrlKey) return; switch (event.code) { case "Escape": if (isSidebarShowing()) hideSidebar(); break; case "BracketRight": if (event.shiftKey) { loadNewLevel({ delta: 6 }); } else { loadNewLevel({ delta: 1 }); } break; case "BracketLeft": if (event.shiftKey) { loadNewLevel({ delta: -6 }); } else { loadNewLevel({ delta: -1 }); } break; case "KeyR": if (event.shiftKey) { loadNewLevel({ shuffle_tiles: false }); checkForDone(); } break; } }); canvas.addEventListener("mousedown", function (event) { if (event.altKey || event.ctrlKey || event.shiftKey) return; if (event.button !== 0) return; event.preventDefault(); if (isSidebarShowing()) { hideSidebar(); return; } switch (game_state) { case GameState.Playing: case GameState.FadeIn: break; case GameState.FadeOut: case GameState.FadeToRoses: return; case GameState.SmellTheRoses: doFadeOut(); return; } var display_x = (event.x - origin_pixel_x) / units_to_pixels; var display_y = (event.y - origin_pixel_y) / units_to_pixels; var wrapped_display_x = euclideanMod(display_x, level.tiles_per_row * level.units_per_tile_x); var wrapped_display_y = euclideanMod(display_y, level.tiles_per_column * level.units_per_tile_y); if (!level.toroidal) { if (display_x !== wrapped_display_x || display_y !== wrapped_display_y) return; } var tile_index = level.getTileIndexFromDisplayPoint(wrapped_display_x, wrapped_display_y); if (!clickTile(tile_index)) return; animateIntoRotation(tile_index); }); var tile_rotation_animations = {}; function animateIntoRotation(tile_index) { var start_time = new Date().getTime(); var total_time = level.tile_animation_time; var existing_animation = tile_rotation_animations[tile_index]; if (existing_animation) cancelAnimationFrame(existing_animation.handle); var animation = { rotation: -1, handle: 0 }; tile_rotation_animations[tile_index] = animation; animate(); function animate() { var time_progress = (new Date().getTime() - start_time) / total_time; if (time_progress >= 1) { delete tile_rotation_animations[tile_index]; renderEverything(); return; } animation.rotation = time_progress - 1; renderEverything(); animation.handle = requestAnimationFrame(animate); } } var units_to_pixels = 100; var origin_pixel_x = -50; var origin_pixel_y = -50; function handleResize() { for (var _i = 0, all_canvases_1 = all_canvases; _i < all_canvases_1.length; _i++) { var c = all_canvases_1[_i]; c.width = window.innerWidth; c.height = window.innerHeight; } var display_width = level.units_per_tile_x * level.display_tiles_x; var display_height = level.units_per_tile_y * level.display_tiles_y; var level_aspect_ratio = display_height / display_width; var canvas_aspect_ratio = canvas.height / canvas.width; units_to_pixels = level_aspect_ratio < canvas_aspect_ratio ? canvas.width / display_width : canvas.height / display_height; var center_x = level.units_per_tile_x * level.tiles_per_row / 2; var center_y = level.units_per_tile_y * level.tiles_per_column / 2; center_x += level.display_offset_x; center_y += level.display_offset_y; origin_pixel_x = canvas.width / 2 - units_to_pixels * center_x; origin_pixel_y = canvas.height / 2 - units_to_pixels * center_y; renderEverything(); } var render_enabled = true; function renderEverything() { if (!render_enabled) return; for (var _i = 0, all_canvases_2 = all_canvases; _i < all_canvases_2.length; _i++) { var c = all_canvases_2[_i]; var ctx = c.getContext("2d"); ctx.clearRect(0, 0, c.width, c.height); } for (var _a = 0, render_target_canvases_1 = render_target_canvases; _a < render_target_canvases_1.length; _a++) { var c = render_target_canvases_1[_a]; var ctx = c.getContext("2d"); ctx.save(); ctx.translate(origin_pixel_x, origin_pixel_y); ctx.scale(units_to_pixels, units_to_pixels); } try { level.renderAsdfBackground(asdf_background_canvas.getContext("2d")); level.renderLevel(tile_canvas.getContext("2d")); level.renderAsdfForeground(asdf_foreground_canvas.getContext("2d")); } finally { for (var _b = 0, render_target_canvases_2 = render_target_canvases; _b < render_target_canvases_2.length; _b++) { var c = render_target_canvases_2[_b]; var ctx = c.getContext("2d"); ctx.restore(); } } var composite_context = buffer_canvas.getContext("2d"); composite_context.save(); try { composite_context.globalAlpha = asdf_alpha; composite_context.drawImage(asdf_background_canvas, 0, 0); composite_context.globalAlpha = 1; composite_context.drawImage(tile_canvas, 0, 0); composite_context.globalAlpha = asdf_alpha; composite_context.drawImage(asdf_foreground_canvas, 0, 0); } finally { composite_context.restore(); } var final_context = canvas.getContext("2d"); final_context.save(); try { final_context.fillStyle = "#fff"; final_context.fillRect(0, 0, canvas.width, canvas.height); final_context.globalAlpha = global_alpha; final_context.drawImage(buffer_canvas, 0, 0); } finally { final_context.restore(); } } var cheatcode_sequence = [ 6, 5, 6, 5, 9, 10, 9, ]; var cheatcode_index = 0; function clickTile(tile_index) { if (level.frozen_tiles[tile_index]) return false; level.touchTile(tile_index); level.rotateTile(tile_index, 1); renderEverything(); if (cheatcode_index !== -1) { if (cheatcode_sequence[cheatcode_index] === tile_index) { cheatcode_index++; if (cheatcode_index === cheatcode_sequence.length) { cheatcode_index = -1; setTimeout(function () { level_number = parseInt("" + prompt("level select"), 10); if (!(0 <= level_number && level_number < 100)) { level_number = 0; } loadNewLevel(); }, 0); return false; } } else { cheatcode_index = -1; } } checkForDone(); save(); return true; } function checkForDone() { var unsolved_count = level.countUnsolved(); if (unsolved_count > 0) return; if (!is_custom_level && unlocked_level_number <= level_number) { unlocked_level_number = level_number + 1; } doFadeToRoses(); } var cancel_state_animation = null; function stopStateAnimation() { if (cancel_state_animation) cancel_state_animation(); cancel_state_animation = null; } function setGameState(new_state) { stopStateAnimation(); global_alpha = 1.0; asdf_alpha = 1.0; game_state = new_state; if (unlocked_level_number < level_number) { unlocked_level_number = level_number; } renderLevelInfoInSidebar(); } function renderLevelInfoInSidebar() { level_down_button.disabled = level_number <= 1; if (level_number >= last_level_number) { level_number_span.innerText = last_level_number + "+"; tile_set_div.classList.remove("hidden"); level_up_button.disabled = true; } else if (level_number >= unlocked_level_number) { level_number_span.innerText = level_number.toString(); level_up_button.disabled = true; } else { level_number_span.innerText = level_number.toString(); level_up_button.disabled = false; } custom_colors_select.value = ColorRules[level.colors]; setElementVisible(custom_colors_div, unlocked_level_number >= 10); setElementVisible(custom_color_two_overlap_option, unlocked_level_number >= 14); custom_shape_select.value = Shape[level.shape]; setElementVisible(custom_shape_div, unlocked_level_number >= 6); custom_toroidal_checkbox.checked = level.toroidal; setElementVisible(custom_toroidal_div, unlocked_level_number >= 16); custom_rough_checkbox.checked = level.rough; setElementVisible(custom_rough_div, unlocked_level_number >= 11); custom_cement_mode_checkbox.checked = level.cement_mode; setElementVisible(custom_cement_mode_div, unlocked_level_number >= 22); var width = level.tiles_per_row; var height = level.tiles_per_column; if (!level.toroidal) { width -= 2; height -= 2; } custom_width_spinner.value = width.toString(); custom_height_spinner.value = height.toString(); adjustSpinnerRules(); } function handleCustomLevelEdited() { is_custom_level = true; loadNewLevel(); adjustSpinnerRules(); } function adjustSpinnerRules() { if (is_custom_level) { level_number_span.innerText = "Custom"; } setElementVisible(level_up_button, !is_custom_level); setElementVisible(level_down_button, !is_custom_level); if (level.shape === Shape.Hexagon && level.toroidal) { custom_width_spinner.min = "2"; custom_width_spinner.step = "2"; custom_height_spinner.min = "2"; custom_height_spinner.step = "2"; } else { custom_width_spinner.step = "1"; custom_width_spinner.min = "1"; custom_height_spinner.step = "1"; custom_height_spinner.min = "1"; } rough_label_span.innerText = level.toroidal ? "Locked Island" : "Rough Edges"; } function setCustomLevelSettingsVisible(visible) { show_custom_level_button.innerText = (visible ? "v" : ">") + " Custom Level"; setElementVisible(level_settings_div, visible); } function doFadeOut() { setGameState(GameState.FadeOut); var start_time = new Date().getTime(); animate(); function animate() { assert(game_state === GameState.FadeOut); var progress = (new Date().getTime() - start_time) / 1000; if (progress < 1) { global_alpha = 1 - progress; asdf_alpha = 0; var handle_1 = requestAnimationFrame(animate); cancel_state_animation = function () { cancelAnimationFrame(handle_1); }; } else { advanceToNextLevel(); doFadeIn(); } renderEverything(); } } function doFadeIn() { setGameState(GameState.FadeIn); line_width_multiplier = 1.0; var start_time = new Date().getTime(); animate(); function animate() { assert(game_state === GameState.FadeIn); var progress = (new Date().getTime() - start_time) / 1000; if (progress < 1) { global_alpha = progress; var handle_2 = requestAnimationFrame(animate); cancel_state_animation = function () { cancelAnimationFrame(handle_2); }; } else { setGameState(GameState.Playing); } renderEverything(); } } function doFadeToRoses() { setGameState(GameState.FadeToRoses); var start_time = new Date().getTime(); animate(); function animate() { assert(game_state === GameState.FadeToRoses); var progress = (new Date().getTime() - start_time) / 1000; if (progress < 1) { asdf_alpha = 1 - progress; line_width_multiplier = 1.0 + progress * line_width_multiplier_factor * +level.perfect_so_far; var handle_3 = requestAnimationFrame(animate); cancel_state_animation = function () { cancelAnimationFrame(handle_3); }; } else { setGameState(GameState.SmellTheRoses); asdf_alpha = 0; line_width_multiplier = 1.0 + line_width_multiplier_factor * +level.perfect_so_far; } renderEverything(); } } function advanceToNextLevel() { render_enabled = false; try { if (is_custom_level) { loadNewLevel(); } else { loadNewLevel({ delta: 1 }); } } finally { render_enabled = true; } doFadeIn(); } var level; function loadNewLevel(opts) { var _a, _b; if ((opts === null || opts === void 0 ? void 0 : opts.delta) != null) { level_number += opts.delta; is_custom_level = false; } else if (is_custom_level) { var parameters = { size: [ clamp(1, parseInt(custom_width_spinner.value, 10), 20), clamp(1, parseInt(custom_height_spinner.value, 10), 20), ], shape: Shape[custom_shape_select.value], colors: ColorRules[custom_colors_select.value], toroidal: custom_toroidal_checkbox.checked, cement_mode: custom_cement_mode_checkbox.checked, rough: custom_rough_checkbox.checked }; if (parameters.shape === Shape.Hexagon && parameters.toroidal) { if (parameters.size[0] % 2 === 1) { parameters.size[0] += 1; } if (parameters.size[1] % 2 === 1) { parameters.size[1] += 1; } } if (!parameters.toroidal) { parameters.size[0] += 2; parameters.size[1] += 2; } parameters.shuffle_tiles = (_a = opts === null || opts === void 0 ? void 0 : opts.shuffle_tiles) !== null && _a !== void 0 ? _a : true; if (parameters.size[0] >= 6 && parameters.size[1] >= 6 && parameters.toroidal && parameters.cement_mode && (parameters.colors == ColorRules.Single && parameters.shape == Shape.Hexagon || !parameters.rough)) { parameters.perfectable = true; } setCurrentLevel(generateLevel(parameters)); return; } setCurrentLevel(getLevelForCurrentLevelNumber((_b = opts === null || opts === void 0 ? void 0 : opts.shuffle_tiles) !== null && _b !== void 0 ? _b : true)); } function setCurrentLevel(new_level) { level = new_level; line_width_multiplier = 1.0; save(); setGameState(GameState.Playing); handleResize(); } function getLevelForCurrentLevelNumber(shuffle_tiles) { if (level_number < 1) level_number = 1; if (level_number > last_level_number) level_number = last_level_number; if (!Number.isInteger(level_number)) level_number = 1; switch (level_number) { case 1: return generateLevel({ size: [4, 4], shape: Shape.Square, colors: ColorRules.Single }, oneColor([ 0, 0, 0, 0, 0, 6, 1, 0, 0, 6, 2, 0, 0, 0, 0, 0, ])); case 2: return generateLevel({ size: [5, 4], shape: Shape.Square, colors: ColorRules.Single }, oneColor([ 0, 0, 0, 0, 0, 0, 6, 14, 12, 0, 0, 3, 9, 4, 0, 0, 0, 0, 0, 0, ])); case 3: return generateLevel({ size: [5, 5], shape: Shape.Square, colors: ColorRules.Single }, oneColor([ 0, 0, 0, 0, 0, 0, 2, 3, 4, 0, 0, 2, 1, 5, 0, 0, 12, 1, 4, 0, 0, 0, 0, 0, 0, ])); } var params = function () { switch (level_number) { case 4: return { size: [7, 7], shape: Shape.Square, colors: ColorRules.Single }; case 5: return { size: [8, 8], shape: Shape.Square, colors: ColorRules.Single }; case 6: return { size: [5, 5], shape: Shape.Hexagon, colors: ColorRules.Single }; case 7: return { size: [6, 6], shape: Shape.Hexagon, colors: ColorRules.Single }; case 8: return { size: [7, 7], shape: Shape.Hexagon, colors: ColorRules.Single }; case 9: return { size: [8, 8], shape: Shape.Hexagon, colors: ColorRules.Single }; case 10: return { size: [7, 7], shape: Shape.Square, colors: ColorRules.TwoSeparate }; case 11: return { size: [9, 9], shape: Shape.Square, colors: ColorRules.TwoSeparate, rough: true }; case 12: return { size: [6, 6], shape: Shape.Hexagon, colors: ColorRules.TwoSeparate }; case 13: return { size: [8, 8], shape: Shape.Hexagon, colors: ColorRules.TwoSeparate, rough: true }; case 14: return { size: [9, 9], shape: Shape.Square, colors: ColorRules.TwoOverlap }; case 15: return { size: [8, 8], shape: Shape.Hexagon, colors: ColorRules.TwoOverlap, rough: true }; case 16: return { size: [6, 6], shape: Shape.Square, colors: ColorRules.TwoOverlap, toroidal: true, rough: true }; case 17: return { size: [6, 6], shape: Shape.Square, colors: ColorRules.TwoSeparate, toroidal: true, rough: true }; case 18: return { size: [6, 6], shape: Shape.Square, colors: ColorRules.Single, toroidal: true, rough: true }; case 19: return { size: [6, 6], shape: Shape.Hexagon, colors: ColorRules.TwoOverlap, toroidal: true, rough: true }; case 20: return { size: [6, 6], shape: Shape.Hexagon, colors: ColorRules.TwoSeparate, toroidal: true, rough: true }; case 21: return { size: [6, 6], shape: Shape.Hexagon, colors: ColorRules.Single, toroidal: true, rough: true }; case 22: return { size: [10, 10], shape: Shape.Square, colors: ColorRules.TwoOverlap, cement_mode: true, rough: true }; case 23: return { size: [9, 9], shape: Shape.Hexagon, colors: ColorRules.TwoOverlap, cement_mode: true, rough: true }; case 24: return { size: [10, 10], shape: Shape.Square, colors: ColorRules.TwoSeparate, cement_mode: true, rough: true }; case 25: return { size: [9, 9], shape: Shape.Hexagon, colors: ColorRules.TwoSeparate, cement_mode: true, rough: true }; case 26: return { size: [10, 10], shape: Shape.Square, colors: ColorRules.Single, cement_mode: true, rough: true }; case 27: return { size: [9, 9], shape: Shape.Hexagon, colors: ColorRules.Single, cement_mode: true, rough: true }; case last_level_number: return { size: [6, 6], shape: Shape.Hexagon, colors: ColorRules.Single, cement_mode: true, toroidal: true, rough: true, perfectable: true }; default: throw new AssertionFailure(); } }(); params.shuffle_tiles = shuffle_tiles; return generateLevel(params); } var last_level_number = 28; function oneColor(values) { var result = []; for (var _i = 0, values_1 = values; _i < values_1.length; _i++) { var value = values_1[_i]; result.push([value]); } return result; } function generateLevel(parameters, tiles) { var _a; var level = new Level(parameters); for (var _i = 0, _b = level.allTileIndexes(); _i < _b.length; _i++) { var tile_index = _b[_i]; if (!level.isInBounds(tile_index)) { level.frozen_tiles[tile_index] = true; } } if (level.rough && level.toroidal) { switch (level.shape) { case Shape.Square: { for (var y = Math.floor((level.tiles_per_column - 1) / 2); y <= Math.floor(level.tiles_per_column / 2); y++) { for (var x = Math.floor((level.tiles_per_row - 1) / 2); x <= Math.floor(level.tiles_per_row / 2); x++) { var tile_index = level.getTileIndexFromCoord(x, y); level.frozen_tiles[tile_index] = true; } } break; } case Shape.Hexagon: { if (level.tiles_per_row & 1) { throw new AssertionFailure(); } else { if (level.tiles_per_column & 1) { throw new AssertionFailure(); } else { level.frozen_tiles[level.getTileIndexFromCoord(level.tiles_per_row / 2 - 1, level.tiles_per_column / 2 - 1)] = true; level.frozen_tiles[level.getTileIndexFromCoord(level.tiles_per_row / 2 - 2, level.tiles_per_column / 2 - 1)] = true; level.frozen_tiles[level.getTileIndexFromCoord(level.tiles_per_row / 2 - 0, level.tiles_per_column / 2 - 1)] = true; level.frozen_tiles[level.getTileIndexFromCoord(level.tiles_per_row / 2 - 1, level.tiles_per_column / 2 - 0)] = true; } } break; } } } if (tiles) { assert(level.tiles_per_row * level.tiles_per_column === tiles.length); for (var _c = 0, tiles_1 = tiles; _c < tiles_1.length; _c++) { var tile = tiles_1[_c]; assert(tile.length === level.color_count); } level.tiles = tiles; } else { assert(level.color_count <= 2); var possible_edge_values = (level.color_count === 1 ? 2 : level.allow_overlap ? 4 : 3); for (var _d = 0, _e = level.allEdges(); _d < _e.length; _d++) { var vector = _e[_d]; var other_tile = level.getTileIndexFromVector(vector.tile_index, vector.direction); var out_of_bounds_count = +!level.isInBounds(vector.tile_index) + +!level.isInBounds(other_tile); if (level.rough) { if (out_of_bounds_count >= 2) continue; } else { if (out_of_bounds_count >= 1) continue; } var edge_value = Math.floor(Math.random() * possible_edge_values); for (var color_index = 0; color_index < level.color_count; color_index++) { if (edge_value & (1 << color_index)) { level.tiles[vector.tile_index][color_index] |= vector.direction; level.tiles[other_tile][color_index] |= level.reverseDirection(vector.direction); } } } if ((_a = parameters.shuffle_tiles) !== null && _a !== void 0 ? _a : true) { for (var _f = 0, _g = level.allTileIndexes(); _f < _g.length; _f++) { var tile_index = _g[_f]; if (!level.frozen_tiles[tile_index]) { level.rotateRandomly(tile_index); } } } } if (parameters.perfectable) { level.initializePossibilityForPerfect(); } return level; } var AssertionFailure = (function () { function AssertionFailure() { } return AssertionFailure; }()); function assert(b) { if (!b) throw new AssertionFailure(); } function euclideanMod(numerator, denominator) { if (numerator < 0) return denominator + (numerator % denominator); return numerator % denominator; } function hashU32(input) { var x = input; x ^= x >> 17; x *= 0xed5ad4bb; x ^= x >> 11; x *= 0xac4c1b51; x ^= x >> 15; x *= 0x31848bab; x ^= x >> 14; return x; } function clamp(min, x, max) { if (x < min) return min; if (x > max) return max; return x; } function setElementVisible(element, isVisible) { if (isVisible) { element.classList.remove("hidden"); } else { element.classList.add("hidden"); } } retry_button.addEventListener("click", function () { loadNewLevel(); hideSidebar(); }); reset_button.addEventListener("click", function () { if (confirm("Really start back at level 1?")) { level_number = 1; is_custom_level = false; unlocked_level_number = 1; loadNewLevel(); hideSidebar(); } }); tile_set_select.addEventListener("input", function () { tile_set = TileSet[tile_set_select.value]; renderEverything(); save(); }); level_down_button.addEventListener("click", function () { loadNewLevel({ delta: -1 }); }); level_up_button.addEventListener("click", function () { loadNewLevel({ delta: 1 }); }); show_custom_level_button.addEventListener("click", function () { if (level_settings_div.classList.contains("hidden")) { setCustomLevelSettingsVisible(true); } else { setCustomLevelSettingsVisible(false); is_custom_level = false; loadNewLevel(); } }); custom_colors_select.addEventListener("input", handleCustomLevelEdited); custom_shape_select.addEventListener("input", handleCustomLevelEdited); custom_toroidal_checkbox.addEventListener("change", handleCustomLevelEdited); custom_rough_checkbox.addEventListener("change", handleCustomLevelEdited); custom_cement_mode_checkbox.addEventListener("change", handleCustomLevelEdited); custom_width_spinner.addEventListener("input", handleCustomLevelEdited); custom_height_spinner.addEventListener("input", handleCustomLevelEdited); function getSaveObject() { var save_data_str = window.localStorage.getItem("loops"); return save_data_str ? JSON.parse(save_data_str) : {}; } function save() { var save_data = getSaveObject(); save_data.level_number = level_number; save_data.is_custom_level = is_custom_level; save_data.unlocked_level_number = unlocked_level_number; save_data.level = { size: [level.tiles_per_row, level.tiles_per_column], shape: level.shape, colors: level.colors, cement_mode: level.cement_mode, toroidal: level.toroidal, rough: level.rough, tiles: level.tiles, frozen_tiles: level.frozen_tiles, recent_touch_queue: level.recent_touch_queue, original_tiles: level.original_tiles, perfect_so_far: level.perfect_so_far }; save_data.tile_set = tile_set_select.value; window.localStorage.setItem("loops", JSON.stringify(save_data)); } (function () { var _a; var save_data = getSaveObject(); level_number = save_data.level_number || 1; is_custom_level = save_data.is_custom_level || false; if (save_data.unlocked_level_number != null) { unlocked_level_number = save_data.unlocked_level_number; } else { if (16 <= level_number && level_number <= 27) { alert("New version of Loops released! Levels 16-27 have been updated, " + "so your progress through level ".concat(level_number, " unfortunately cannot be loaded. :( ") + "You've been set back to level 16. Please enjoy the new sequence of levels! " + "(And don't forget to check the sidebar for cool new stuff!) :)"); level_number = 16; delete save_data.level; } unlocked_level_number = level_number; } tile_set = (_a = TileSet[save_data.tile_set]) !== null && _a !== void 0 ? _a : TileSet.Trypo; tile_set_select.value = TileSet[tile_set]; function loadLevelData() { if (typeof save_data.level !== 'object') return null; var size = save_data.level.size; if (!Array.isArray(size)) return null; if (size.length !== 2) return null; for (var _i = 0, size_1 = size; _i < size_1.length; _i++) { var x = size_1[_i]; if (!Number.isInteger(x)) return null; if (!(2 <= x && x <= 20)) return null; } var shape = save_data.level.shape; if (typeof Shape[shape] !== 'string') return null; var colors = save_data.level.colors; if (typeof ColorRules[colors] !== 'string') return null; var cement_mode = save_data.level.cement_mode; if (typeof cement_mode !== 'boolean') return null; var toroidal = save_data.level.toroidal; if (typeof toroidal !== 'boolean') return null; var rough = save_data.level.rough; if (typeof rough !== 'boolean') return null; var level_parameters = { size: size, shape: shape, colors: colors, cement_mode: cement_mode, toroidal: toroidal, rough: rough }; var tiles = save_data.level.tiles; if (!Array.isArray(tiles)) return null; if (tiles.length !== size[0] * size[1]) return null; var color_count = (function () { switch (colors) { case ColorRules.Single: return 1; case ColorRules.TwoSeparate: return 2; case ColorRules.TwoOverlap: return 2; default: throw new AssertionFailure(); } })(); var color_mask = (function () { switch (shape) { case Shape.Square: return (1 << 4) - 1; case Shape.Hexagon: return (1 << 6) - 1; default: throw new AssertionFailure(); } })(); for (var _a = 0, tiles_2 = tiles; _a < tiles_2.length; _a++) { var x = tiles_2[_a]; if (!Array.isArray(x)) return null; if (x.length !== color_count) return null; for (var i = 0; i < x.length; i++) { var c = x[i]; if (!Number.isInteger(c)) return null; x[i] = c & color_mask; } } var frozen_tiles = save_data.level.frozen_tiles; if (typeof frozen_tiles !== 'object') return null; var recent_touch_queue = save_data.level.recent_touch_queue; if (!Array.isArray(recent_touch_queue)) return null; if (recent_touch_queue.length > 3) return null; var perfect_so_far = false; var original_tiles = save_data.level.original_tiles; if (original_tiles != null) { if (!Array.isArray(original_tiles)) return null; if (original_tiles.length !== size[0] * size[1]) return null; for (var _b = 0, original_tiles_1 = original_tiles; _b < original_tiles_1.length; _b++) { var x = original_tiles_1[_b]; if (!Array.isArray(x)) return null; } perfect_so_far = save_data.level.perfect_so_far; if (typeof perfect_so_far !== "boolean") return null; } var loaded_level = new Level(level_parameters); loaded_level.tiles = tiles; loaded_level.frozen_tiles = frozen_tiles; loaded_level.recent_touch_queue = recent_touch_queue; loaded_level.original_tiles = original_tiles; loaded_level.perfect_so_far = perfect_so_far; return loaded_level; } var loaded_level = loadLevelData(); if (loaded_level != null) { level = loaded_level; setGameState(GameState.Playing); handleResize(); checkForDone(); setCustomLevelSettingsVisible(is_custom_level); } else { loadNewLevel(); } })(); //# sourceMappingURL=a.js.map