diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..eade8b4 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,18 @@ +name: CI + +on: + pull_request: + branches: [ main ] + +jobs: + validate: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Validate Commands JSON structure + run: node ./internal/validate-commands-json.js + + - name: Validate Commands + run: node ./internal/validate-commands.js \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..554a8f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..71084fe --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# GitHub Minesweeper + +Gain hands-on experience with a professional Git workflow used in many real-world teams with the help of a bot teammate. Find more information at [Profy.dev](https://profy.dev/project/github-minesweeper). diff --git a/commands.json b/commands.json new file mode 100644 index 0000000..ac225f0 --- /dev/null +++ b/commands.json @@ -0,0 +1,3 @@ +{ + "commands": [] +} \ No newline at end of file diff --git a/internal/game b/internal/game new file mode 100644 index 0000000..b273653 --- /dev/null +++ b/internal/game @@ -0,0 +1 @@ +eyJncmlkIjpbW3sieHBvcyI6MCwieXBvcyI6MCwidmFsdWUiOjAsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjEsInlwb3MiOjAsInZhbHVlIjowLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjoyLCJ5cG9zIjowLCJ2YWx1ZSI6MSwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6MywieXBvcyI6MCwidmFsdWUiOjEsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjQsInlwb3MiOjAsInZhbHVlIjoyLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjo1LCJ5cG9zIjowLCJ2YWx1ZSI6Ijpib21iOiIsImlzTWluZSI6dHJ1ZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6NiwieXBvcyI6MCwidmFsdWUiOiI6Ym9tYjoiLCJpc01pbmUiOnRydWUsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjcsInlwb3MiOjAsInZhbHVlIjoiOmJvbWI6IiwiaXNNaW5lIjp0cnVlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX1dLFt7Inhwb3MiOjAsInlwb3MiOjEsInZhbHVlIjoxLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjoxLCJ5cG9zIjoxLCJ2YWx1ZSI6MiwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6MiwieXBvcyI6MSwidmFsdWUiOjIsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjMsInlwb3MiOjEsInZhbHVlIjoiOmJvbWI6IiwiaXNNaW5lIjp0cnVlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjo0LCJ5cG9zIjoxLCJ2YWx1ZSI6MiwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6NSwieXBvcyI6MSwidmFsdWUiOjIsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjYsInlwb3MiOjEsInZhbHVlIjozLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjo3LCJ5cG9zIjoxLCJ2YWx1ZSI6MiwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9XSxbeyJ4cG9zIjowLCJ5cG9zIjoyLCJ2YWx1ZSI6Ijpib21iOiIsImlzTWluZSI6dHJ1ZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6MSwieXBvcyI6MiwidmFsdWUiOjIsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjIsInlwb3MiOjIsInZhbHVlIjoiOmJvbWI6IiwiaXNNaW5lIjp0cnVlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjozLCJ5cG9zIjoyLCJ2YWx1ZSI6MiwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6NCwieXBvcyI6MiwidmFsdWUiOjEsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjUsInlwb3MiOjIsInZhbHVlIjowLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjo2LCJ5cG9zIjoyLCJ2YWx1ZSI6MCwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6NywieXBvcyI6MiwidmFsdWUiOjAsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfV0sW3sieHBvcyI6MCwieXBvcyI6MywidmFsdWUiOjEsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjEsInlwb3MiOjMsInZhbHVlIjoyLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjoyLCJ5cG9zIjozLCJ2YWx1ZSI6MSwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6MywieXBvcyI6MywidmFsdWUiOjEsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjQsInlwb3MiOjMsInZhbHVlIjowLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjo1LCJ5cG9zIjozLCJ2YWx1ZSI6MCwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6NiwieXBvcyI6MywidmFsdWUiOjAsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjcsInlwb3MiOjMsInZhbHVlIjowLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX1dLFt7Inhwb3MiOjAsInlwb3MiOjQsInZhbHVlIjowLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjoxLCJ5cG9zIjo0LCJ2YWx1ZSI6MCwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6MiwieXBvcyI6NCwidmFsdWUiOjAsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjMsInlwb3MiOjQsInZhbHVlIjowLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjo0LCJ5cG9zIjo0LCJ2YWx1ZSI6MCwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6NSwieXBvcyI6NCwidmFsdWUiOjAsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjYsInlwb3MiOjQsInZhbHVlIjoxLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjo3LCJ5cG9zIjo0LCJ2YWx1ZSI6MSwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9XSxbeyJ4cG9zIjowLCJ5cG9zIjo1LCJ2YWx1ZSI6MCwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6MSwieXBvcyI6NSwidmFsdWUiOjAsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjIsInlwb3MiOjUsInZhbHVlIjowLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjozLCJ5cG9zIjo1LCJ2YWx1ZSI6MSwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6NCwieXBvcyI6NSwidmFsdWUiOjEsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjUsInlwb3MiOjUsInZhbHVlIjoxLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjo2LCJ5cG9zIjo1LCJ2YWx1ZSI6MiwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6NywieXBvcyI6NSwidmFsdWUiOiI6Ym9tYjoiLCJpc01pbmUiOnRydWUsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfV0sW3sieHBvcyI6MCwieXBvcyI6NiwidmFsdWUiOjAsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjEsInlwb3MiOjYsInZhbHVlIjoxLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjoyLCJ5cG9zIjo2LCJ2YWx1ZSI6MSwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6MywieXBvcyI6NiwidmFsdWUiOjIsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjQsInlwb3MiOjYsInZhbHVlIjoiOmJvbWI6IiwiaXNNaW5lIjp0cnVlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjo1LCJ5cG9zIjo2LCJ2YWx1ZSI6MSwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6NiwieXBvcyI6NiwidmFsdWUiOjIsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjcsInlwb3MiOjYsInZhbHVlIjoiOmJvbWI6IiwiaXNNaW5lIjp0cnVlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX1dLFt7Inhwb3MiOjAsInlwb3MiOjcsInZhbHVlIjowLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjoxLCJ5cG9zIjo3LCJ2YWx1ZSI6MSwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6MiwieXBvcyI6NywidmFsdWUiOiI6Ym9tYjoiLCJpc01pbmUiOnRydWUsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjMsInlwb3MiOjcsInZhbHVlIjoyLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjo0LCJ5cG9zIjo3LCJ2YWx1ZSI6MSwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9LHsieHBvcyI6NSwieXBvcyI6NywidmFsdWUiOjEsImlzTWluZSI6ZmFsc2UsImlzUmV2ZWFsZWQiOmZhbHNlLCJpc0ZsYWdnZWQiOmZhbHNlfSx7Inhwb3MiOjYsInlwb3MiOjcsInZhbHVlIjoxLCJpc01pbmUiOmZhbHNlLCJpc1JldmVhbGVkIjpmYWxzZSwiaXNGbGFnZ2VkIjpmYWxzZX0seyJ4cG9zIjo3LCJ5cG9zIjo3LCJ2YWx1ZSI6MSwiaXNNaW5lIjpmYWxzZSwiaXNSZXZlYWxlZCI6ZmFsc2UsImlzRmxhZ2dlZCI6ZmFsc2V9XV0sImdhbWVDb3VudCI6MSwibWluZXNGb3VuZCI6MCwiZmFsc2VNaW5lcyI6MCwic3RhdHVzIjoiT05HT0lORyIsIm1vdmVzTWFkZSI6MCwib3B0aW9ucyI6eyJyb3dzIjo4LCJjb2xzIjo4LCJtaW5lcyI6MTB9fQ== diff --git a/internal/handle-error.js b/internal/handle-error.js new file mode 100644 index 0000000..24357e3 --- /dev/null +++ b/internal/handle-error.js @@ -0,0 +1,6 @@ +function handleError(message) { + console.error(`\n\n${message}\n\n`); + process.exit(1); +}; + +module.exports = handleError; \ No newline at end of file diff --git a/internal/validate-commands-json.js b/internal/validate-commands-json.js new file mode 100644 index 0000000..bba0a23 --- /dev/null +++ b/internal/validate-commands-json.js @@ -0,0 +1,26 @@ +const fs = require('fs'); +const handleError = require('./handle-error'); + +const commandsFile = fs.readFileSync('./commands.json'); +let commandsJson; +try { + commandsJson = JSON.parse(commandsFile); +} catch (e) { + if (e instanceof SyntaxError) { + handleError(`The commands.json file contains a syntax error: ${e.message}`); + return; + } + throw e; +} + +if (!commandsJson.commands || !Array.isArray(commandsJson.commands)) { + handleError('The structure of the commands object is broken.'); + return; +} + +commandsJson.commands.forEach(command => { + if (typeof command !== 'string') { + handleError('Each item in the commands array must be a string'); + return; + } +}) \ No newline at end of file diff --git a/internal/validate-commands.js b/internal/validate-commands.js new file mode 100644 index 0000000..1c3143e --- /dev/null +++ b/internal/validate-commands.js @@ -0,0 +1,44 @@ +const fs = require('fs'); +const handleError = require('./handle-error'); + +const VALID_COMMANDS = { + CLEAR: 'CLEAR', + FLAG: 'FLAG', + UNFLAG: 'UNFLAG', + END: 'END' +}; +const NUM_ROWS = 8; +const NUM_COLS = 8; + +const commandsFile = fs.readFileSync('./commands.json'); +const { commands } = JSON.parse(commandsFile) + +commands.forEach(command => { + const [action, cellId] = command.toUpperCase().split(/\s+/g); + if (!Object.values(VALID_COMMANDS).includes(action)) { + handleError(`Invalid command "${command}"`); + return; + } + + if (action === VALID_COMMANDS.END) { + return; + } + + const posX = Number(cellId.charCodeAt(0) - "A".charCodeAt(0)); + const posY = Number.parseInt(cellId.charAt(1), 10) - 1; + + if (isNaN(posY)) { + handleError(`${command}: The second character ${cellId.charAt(1)} in ${cellId} has to be a number`); + return; + } + + if ( + posX < 0 || + posX >= NUM_COLS || + posY < 0 || + posY >= NUM_ROWS + ) { + handleError(`${command}: Field ${cellId} does not exist.`); + return; + } +}) \ No newline at end of file