{"id":19135853,"url":"https://github.com/glasstoestudio/oop-final-project-lights-out","last_synced_at":"2026-06-20T17:32:03.918Z","repository":{"id":260826870,"uuid":"882372666","full_name":"GlassToeStudio/OOP-Final-Project-Lights-Out","owner":"GlassToeStudio","description":"Public Repo for Final Project","archived":false,"fork":false,"pushed_at":"2024-12-09T01:21:23.000Z","size":3365,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-12T17:08:57.901Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GlassToeStudio.png","metadata":{"files":{"readme":"README.MD","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-02T16:52:47.000Z","updated_at":"2024-12-09T01:21:27.000Z","dependencies_parsed_at":"2024-11-02T21:23:18.120Z","dependency_job_id":"f27d75c7-7ce5-4842-8230-1126f93048b5","html_url":"https://github.com/GlassToeStudio/OOP-Final-Project-Lights-Out","commit_stats":null,"previous_names":["glasstoestudio/oop-final-project-lights-out"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/GlassToeStudio/OOP-Final-Project-Lights-Out","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlassToeStudio%2FOOP-Final-Project-Lights-Out","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlassToeStudio%2FOOP-Final-Project-Lights-Out/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlassToeStudio%2FOOP-Final-Project-Lights-Out/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlassToeStudio%2FOOP-Final-Project-Lights-Out/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GlassToeStudio","download_url":"https://codeload.github.com/GlassToeStudio/OOP-Final-Project-Lights-Out/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlassToeStudio%2FOOP-Final-Project-Lights-Out/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34580039,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T06:32:25.818Z","updated_at":"2026-06-20T17:32:03.895Z","avatar_url":"https://github.com/GlassToeStudio.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lights Out\nThis is an implementation of the game \"Lights Out\" in built with Windows Forms.\n\n## Installation and Requirements:\n * Clone the repo:\n\n ```bash\ngit clone https://github.com/GlassToeStudio/OOP-Final-Project-Lights-Out.git\n ```\n#### Requirements:\n * Windows 10.X +\n * VS 2022\n * .NET8\n * Package: Newtonsoft.Json\n    - Two options:\n        - Tools/NuGet Package Manager/Package Manager Console/\n            ```bash\n                dotnet restore \n            ```\n        - Tools/NuGet Package Manager/Manage NuGet Packages for Solution\n            * Search: `newtonsoft.json` and install.\n\n\n# Overview\n\nLights Out is a digital game released in 1995 by Tiger Electronics. The original design featured a 5 by 5 grid of lights. When the game starts the lights are randomly toggled on or off. Pressing any light will toggle its state and its neighbors state (On =\u003e Off, Off =\u003e On). The objective is turn off all of the lights with as few clicks as possible.\n\n\n![GamePlay](GithubFiles/GamePlay.gif)\n\n\n## Development\n\nLevels are stored in .json format With Level number, Level size (3 for a 3x3 board), Minimum moves, which would be the least number of moves used to beat the level, BestScore to keep a record of the least moves used to beat the level, Stars (int) and StarsText (string) which are used to display up to three stars on the UI, 3 is the best 0 is the worst, a Name field used to display level name on the UI, and a array of integers called board, this is used for the initial condition (0 is Off and 1 is On).\n\n\n```json\n{\n    \"Level\": 1,\n    \"Size\": 3,\n    \"MinMoves\": 2,\n    \"BestScore\": 9000,\n    \"Stars\": 0,\n    \"Board\": [\n    0,\n    1,\n    0,\n    0,\n    1,\n    0,\n    0,\n    0,\n    1\n    ],\n    \"Name\": \"Level 1\",\n    \"StarText\": \"☆☆☆\"\n}\n```\n\nThis data is read from disk and deserialized into an c# struct.\n\n```cs\npublic struct LevelData\n{\n    /// \u003csummary\u003eReturn \"Level {level}\" Ex: \"Level 1\".\u003c/summary\u003e\n    public readonly string Name =\u003e $\"Level {Level}\";\n    /// \u003csummary\u003eThe number of this level. Ex: Level 1.\u003c/summary\u003e\n    public int Level;\n    /// \u003csummary\u003eSize of board Ex: Size = 4 =\u003e 4x4 board.\u003c/summary\u003e\n    public int Size;\n    /// \u003csummary\u003eMinimum moves required to complete the level.\u003c/summary\u003e\n    public int MinMoves;\n    /// \u003csummary\u003eThe minimum number of moves used to beat this level. 9000 to begin.\u003c/summary\u003e\n    public int BestScore = 9000;\n    /// \u003csummary\u003eThe number of Stars earned for this level. 0 to begin.\u003c/summary\u003e\n    public int Stars = 0;\n    /// \u003csummary\u003eInteger array representing a data model of the Board.\u003c/summary\u003e\n    public int[] Board = [];\n```\n\n\nSome additional functionality is added to the struct by way of properties and methods.\n\n\n```cs\n/// \u003csummary\u003e\n/// Update Board data to match the View data. \n/// \u003c/summary\u003e\n/// \u003cparam name=\"lights\"\u003eArray of Lights that is the interactive View.\u003c/param\u003e\npublic readonly void UpdateBoard(Light[] lights)\n{\n    foreach (var light in lights)\n    {\n        Board[light.Index] = (int)light.State;\n    }\n}\n\n/// \u003csummary\u003e\n/// Get a UI friendly string of stars based on number of stars earned for this level.\n/// Ex: Stars = 2, StarText = \"★★☆\"\n/// \u003c/summary\u003e\npublic readonly string StarText =\u003e\n\n    this.Stars switch\n    {\n        0 =\u003e \"☆☆☆\",\n        1 =\u003e \"★☆☆\",\n        2 =\u003e \"★★☆\",\n        3 =\u003e \"★★★\",\n        _ =\u003e \"☆☆☆\",\n    };\n```\n\nAll inital levels are stored an a larger json object.\n\n\n```json\n{\n  \"Levels\": [\n    {\n      \"Level\": 1,\n      \"Size\": 3,\n      \"MinMoves\": 2,\n      \"BestScore\": 9000,\n      \"Stars\": 0,\n      \"Board\": [\n        0,\n        1,\n        0,\n        0,\n        1,\n        0,\n        0,\n        0,\n        1\n      ],\n      \"Name\": \"Level 1\",\n      \"StarText\": \"☆☆☆\"\n    },\n    {\n      \"Level\": 2,\n      \"Size\": 3,\n      \"MinMoves\": 3,\n      \"BestScore\": 9000,\n      \"Stars\": 0,\n      \"Board\": [\n        0,\n        1,\n        0,\n        1,\n        1,\n        0,\n        1,\n        1,\n        1\n      ],\n      \"Name\": \"Level 2\",\n      \"StarText\": \"☆☆☆\"\n    }\n}\n```\n\nWhich is held in a simple struct, LevelDatabase. LevelDatabase has a List of LevelData and a index accessor.\n\n```cs\n    /// \u003csummary\u003e\n    /// Container for all pre-made levels in a List of \u003csee cref=\"LevelData\"/\u003e. Implements \u003csee cref=\"IDatabase\"/\u003e.\n    /// \u003cpara\u003eThe  \u003csee cref=\"LevelDatabase\"/\u003e holds data for each pre-made Level in the game.\n    /// The data is loaded at startup and new levels are initialized with their respective\n    /// \u003csee cref=\"LevelData\"/\u003e objects information.\u003c/para\u003e\n    /// \u003c/summary\u003e\n    public struct LevelDatabase : IDatabase\n    {\n        /// \u003csummary\u003e\n        /// List of all pre-made \u003csee cref=\"LevelData\"/\u003e. Levels are initialized with this data.\n        /// \u003c/summary\u003e\n        public List\u003cLevelData\u003e Levels { get; set; }\n\n        /// \u003cinheritdoc/\u003e\n        public readonly LevelData this[int index] =\u003e Levels[index];\n\n        /// \u003csummary\u003e\n        /// Initializes a new instance of the \u003csee cref=\"LevelDatabase\"/\u003e struct.\n        /// \u003c/summary\u003e\n        public LevelDatabase() =\u003e Levels = [];\n    }\n}\n```\n\nThis data is loaded from disk when the game starts and all Levels are based off this initial state.\n\n\nAnd extension method helps load the data.\n\n```cs\npublic static class DatabaseExtensions\n{\n    /// \u003csummary\u003e\n    /// Load game data from disk for this LevelDatabase Object.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"gameDb\"\u003ethis level \u003csee cref=\"LevelDatabase\"/\u003e\u003c/param\u003e\n    /// \u003creturns\u003e\u003c/returns\u003e\n    public static LevelDatabase LoadLevelDatabase(this LevelDatabase gameDb)\n    {\n        return gameDb.LoadDatabase\u003cLevelDatabase\u003e(\"Game.json\");\n    }\n\n    ...\n\n    /// \u003csummary\u003e\n    /// Load database from disk.\n    /// \u003c/summary\u003e\n    /// \u003ctypeparam name=\"T\"\u003eLevelDatabase or UserDatabase.\u003c/typeparam\u003e\n    /// \u003cparam name=\"db\"\u003ethis database\u003c/param\u003e\n    /// \u003cparam name=\"databaseName\"\u003edatabase name in .json format: User.json or Game.json.\u003c/param\u003e\n    /// \u003creturns\u003ethis Database populated with data from disk.\u003c/returns\u003e\n    private static T LoadDatabase\u003cT\u003e(this T db, string databaseName)\n    {\n        string jsonString;\n        using (var streamReader = new StreamReader(FileUtil.GetDatabase(databaseName)))\n        {\n            jsonString = streamReader.ReadToEnd();\n        }\n        return JsonConvert.DeserializeObject\u003cT\u003e(jsonString);\n    }\n}\n```\n\nUsed like:\n\n```cs\nprivate LevelDatabase game = new LevelDatabase().LoadLevelDatabase();\n```\n\n### Level Saving\n\nUser data is managed in a similar way. A UserDatabase struct holds the data like the LevelDatabase struct, but with some extra functionality for updating and adding data.\n\n```cs\n    /// \u003csummary\u003e\n    /// Database for holding user save data for each level and general game play. Implements \u003csee cref=\"IDatabase\"/\u003e.\n    /// The data is loaded at startup. Completed levels are added and updated for user records.\n    /// /// \u003c/summary\u003e\n    public struct UserDatabase : IDatabase\n    {\n        /// \u003csummary\u003e\n        /// List of \u003csee cref=\"LevelData\"/\u003e. Holds users save data for each level.\n        /// \u003c/summary\u003e\n        public List\u003cLevelData\u003e Levels { get; set; }\n        /// \u003csummary\u003e\n        /// The index of the current selected level in the  \u003csee cref=\"IDatabase\"/\u003e.\n        /// \u003c/summary\u003e\n        public int SelectedIndex { get; set; } = 0;\n        /// \u003csummary\u003e\n        /// The maximum index selectable in the  \u003csee cref=\"IDatabase\"/\u003e. Indicates highest unlocked level.\n        /// \u003c/summary\u003e\n        public int MaxIndex { get; set; } = 0;\n        /// \u003csummary\u003e\n        /// The current selected level.\n        /// \u003c/summary\u003e\n        public readonly LevelData CurrentLevel =\u003e Levels[SelectedIndex];\n        /// \u003cinheritdoc/\u003e\n        public readonly LevelData this[int index] =\u003e Levels[index];\n\n        /// \u003csummary\u003e\n        /// Initializes a new instance of the \u003csee cref=\"UserDatabase\"/\u003e struct.\n        /// \u003c/summary\u003e\n        public UserDatabase() =\u003e Levels = [];\n\n        /// \u003csummary\u003e\n        /// Add a level to the database. When a level is first cleared, the level at the next index is added to this Levels list.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"levelData\"\u003e\u003c/param\u003e\n        public readonly void AddLevel(LevelData levelData)\n        {\n            Levels.Add(levelData);\n        }\n\n        /// \u003csummary\u003e\n        /// Update the \u003csee cref=\"LevelData\"/\u003e object for the level loaded at the \u003csee cref=\"SelectedIndex\"/\u003e with new data.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"levelData\"\u003eUpdated \u003csee cref=\"LevelData\"/\u003e to overwrite the data at the current levels index.\u003c/param\u003e\n        public readonly void UpdateLevel(LevelData levelData)\n        {\n            Levels[SelectedIndex] = levelData;\n        }\n    }\n```\n\nIts .json data is as follows. This is the initial state for a new user. One level is added to Levels and its at the initial state. Once played this data is updated and upon beating a level the next level is added to the list. The current level that is selected when the user closes the application is stored and is loaded back when the user plays again. \n\n```json\n{\n  \"Levels\": [\n    {\n      \"Level\": 1,\n      \"Size\": 3,\n      \"MinMoves\": 2,\n      \"BestScore\": 9000,\n      \"Stars\": 0,\n      \"Board\": [\n        0,\n        1,\n        0,\n        0,\n        1,\n        0,\n        0,\n        0,\n        1\n      ],\n      \"Name\": \"Level 1\",\n      \"StarText\": \"☆☆☆\"\n    }\n  ],\n  \"SelectedIndex\": 0,\n  \"MaxIndex\": 0,\n  \"CurrentLevel\": {\n      \"Level\": 1,\n      \"Size\": 3,\n      \"MinMoves\": 2,\n      \"BestScore\": 9000,\n      \"Stars\": 0,\n      \"Board\": [\n        0,\n        1,\n        0,\n        0,\n        1,\n        0,\n        0,\n        0,\n        1\n      ],\n      \"Name\": \"Level 1\",\n      \"StarText\": \"☆☆☆\"\n    }\n}\n```\n\nLike the LevelDatabase, the UserDatabase is loaded via an extension method:\n\n```cs\n    /// \u003csummary\u003e\n    /// Load user data from disk for this UserDatabase Object.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"userDb\"\u003ethis \u003csee cref=\"UserDatabase\"/\u003e\u003c/param\u003e\n    /// \u003creturns\u003e\u003c/returns\u003e\n    public static UserDatabase LoadUserDatabase(this UserDatabase userDb)\n    {\n        return userDb.LoadDatabase\u003cUserDatabase\u003e(\"User.json\");\n    }\n```\n\nUsed like:\n\n```cs\nprivate UserDatabase user = new UserDatabase().LoadUserDatabase();\n\n```\n\nBoth LevelDatabase and UserDatabase implement the interface IDatabase. IDatabase ensures each object has a list of LevelData and an indexer for the list.\n\n```cs\n    /// \u003csummary\u003e\n    /// Provides List of \u003csee cref=\"LevelData\"/\u003e objects and an indexer for accesing \u003csee cref=\"LevelData\"/\u003e objects in List.\n    /// \u003c/summary\u003e\n    public interface IDatabase\n    {\n        /// \u003csummary\u003e\n        /// List of \u003csee cref=\"LevelData\"/\u003e objects containing preset data for each level in the game.\n        /// \u003c/summary\u003e\n        public List\u003cLevelData\u003e Levels { get; set; }\n\n        /// \u003csummary\u003e\n        /// Get a \u003csee cref=\"LevelData\"/\u003e object for a specific level by index from the \u003csee cref=\"IDatabase\"/\u003e database.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"index\"\u003e Index of level in list. Ex: Level 1 index is 0\u003c/param\u003e\n        /// \u003creturns\u003eThe \u003csee cref=\"LevelData\"/\u003e at the selected index.\u003c/returns\u003e\n        public LevelData this[int index] =\u003e Levels[index];\n    }\n```\n\nWe pass the IDatabase object to a SaveDatabase method when saving user data to disk. This is mainly used for UserDatabase but can accept a LevelDatabase since they are both IDatabase objects.\n\n```cs\n    /// \u003csummary\u003e\n    /// Save the given IDatabase object to disk. Saves to internal program resources directory.Saves to working project directory for development if DEBUG.\n    /// \u003cpara\u003eFile extension \".json\" is appended to file name so pass file anme without extension.\u003c/para\u003e\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"database\"\u003eThe database to serialize to disk. GameDatabase or UserDatabase.\u003c/param\u003e\n    /// \u003cparam name=\"databaseName\"\u003eThe name of the file that is written to disk, \"Game\" or\"User\"\u003c/param\u003e\n    private static void SaveDatabase(IDatabase database, string databaseName)\n    {\n        var data = JsonConvert.SerializeObject(database);\n        // Write to internal directory\n        File.WriteAllText(GetDatabase($\"{databaseName}.json\"), data);\n\n        // Write to project directory to keep project and repo up to date with changes.\n        File.WriteAllText($\"{PROJECT_DIR}{databaseName}.json\", data);\n    }\n```\n\nAll data is managed via a DataHandler object which loads all data from disk, maintains current level and performs any logic required to determine which LevelData is returned and how to update user data and save to disk.\n\n```cs\n/// \u003csummary\u003e\n/// Class for handling game data. \n/// Loading data from disk,\n/// Incrementing and decrement level,\n/// Updating user progress,\n/// Saving data to disk.\n/// \u003c/summary\u003e\npublic class DataHandler()\n{\n    /// Load UserDatabase and GameDatabase from disk.\n    private UserDatabase user = new UserDatabase().LoadUserDatabase();\n    private LevelDatabase game = new LevelDatabase().LoadLevelDatabase();\n        \n    ...\n\n```\n\n### Level Generation\n\nThere are 60 pre-made levels for this game. Twenty 3x3, twenty 4x4, and twenty 5x5. Some were manually created by choosing what 'light', index, should be zero and what would be one. Some were created with the random level generator. \n\n#### Manual\n\nManual Level data is generated by using the built game board and turning on/off lights at specific indices to create a pattern. This data is kept in the LevelData object and saved to disk as a .json. This allowed for a high level of control and ensured levels were solvable. Once could also generate unique patterns for cool looking levels. \n\n#### Random Generation\n\nA level generator was also created to assist with level creation. The level generator uses the built-in pre-built game board and chooses, at random, which light (index) to turn, on. There is some level of control of a predetermined number of minimum moves required to solve a level and/or how many lights should start as on and off. \n\n```cs\n        #region Generation\n        private void GenerateRandomLevel()\n        {\n            moves = 0;\n            int size = GetBoardSizeForRandomGen();\n            GenerateGameBoardsAndSelect();\n            Random rnd = new();\n            int minMoves = -1;\n\n            // numMinMoves is a NumericUpDown control where we have seleected a desired\n            // value for minimum moves. Loop until we have a solution that equals our\n            // desired value.\n            while (minMoves != numMinMoves.Value)\n            {\n                foreach (var light in lights)\n                {\n                    light.TurnOff();\n                }\n\n                List\u003cint\u003e used = [];\n                int iterations = rnd.Next(size * size + 1);\n                for (int i = 0; i \u003c iterations; i++)\n                {\n                    int randLight = rnd.Next(0, size * size);\n\n                    // We do this so that we can only ever touch each light once.\n                    while (used.Contains(randLight))\n                    {\n                        randLight = rnd.Next(0, size * size);\n                    }\n                    used.Add(randLight);\n\n                    lights[randLight].ClickLight();\n                }\n\n                levelData = new LevelData(handler.Levels.Count, size, 0);\n                levelData.UpdateBoard(lights);\n                levelData.MinMoves = Solver.GetSolutionMatrix(levelData).Sum();\n                lblLog.Text = DebugBoardState();\n\n                // If we did not select a value for min moves, take the current solution.\n                if (numMinMoves.Value == 0)\n                {\n                    break;\n                }\n            }\n\n            btnSaveLevel.Enabled = true;\n           \n            txtFileName.Text = $\"{levelData}_generated\";\n        }\n```\n\n### Solver\n\nTo assist with testing, a puzzle solver is programmed to solve the puzzle in as few moves as possible. The solver is also used with the manual and random level generation to calculate minimum moves to solve. \n\n\n### Debug Options","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglasstoestudio%2Foop-final-project-lights-out","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglasstoestudio%2Foop-final-project-lights-out","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglasstoestudio%2Foop-final-project-lights-out/lists"}