Skip to main content
added 184 characters in body; deleted 12 characters in body; added 152 characters in body
Source Link
Ale_Bianco
  • 665
  • 2
  • 9
  • 26

Note: The numbers on the board are generated just like in a real Minesweeper game, but they’re purely cosmetic — they don’t encode any part of the message. The real data is entirely in the pattern of mines (*) and empty cells (.), read left to right, row by row.

function textToBinary(text) {
  return text
    .split('')
             .map(c => c.charCodeAt(0).toString(2).padStart(8, '0'))
             .join('');
}

function binaryToGrid(binary, size = 9) {
  const maxBits = size * size;
  binary = binary.slice(0, maxBits);
  const grid = Array.from({ length: size }, () => Array(size).fill('.'));
  for (let i = 0; i < Math.min(binary.length, size * size);length; i++) {
    const row = Math.floor(i / size);
    const col = i % size;
    grid[row][col] = binary[i] === '1' ? '*' : '.';
  }
  return grid;
}

function computeHints(grid) {
  const size = grid.length;
  const result = grid.map(row => row.slice());

  const directionsdirs = [-1, 0, 1];
  for (let r = 0; r < size; r++) {
    for (let c = 0; c < size; c++) {
      if (grid[r][c] === '*') continue;
      let count = 0;
      for (let dr of directionsdirs) {
        for (let dc of directionsdirs) {
          if (dr === 0 && dc === 0) continue;
          const nr = r + dr, nc = c + dc;
          if (nr >= 0 && nr < size && nc >= 0 && nc < size && grid[nr][nc] === '*') {
            count++;
          }
        }
      }
      if (count > 0) result[r][c] = count.toString();
    }
  }
  return result;
}

function renderGrid(grid) {
  return grid.map(row => row.join(' ')).join('\n');
}

// Exampleexample
const message = "TREASURE"; 
const binary = textToBinary(message);
const mines = binaryToGrid(binary, 9); 
const hintGrid = computeHints(mines);

console.log("Minesweeper encoding of:", message, "\n");
console.log(renderGrid(hintGrid)); 

When I run the script withLive demo and console output are now synchronized: both use the message "TREASURE"same bit padding and encoding logic, I get:

Minesweeper encoding of: TREASURE

* . . 1 1 1 . . .
. . . 1 * 2 1 . .
. * . 1 2 * 2 1 .
. . . . 1 2 * 2 1
. . . . . 1 2 * 1
. . . . . . 1 2 *
. . . . . . . 1 2

To decode:

  • Read each cell left to right, top to bottom.
  • * = 1, . = 0. Ignore the numbers.
  • Recombine the bits into 8-bit characters.

The result? "TREASURE"ensuring that examples match exactly. Thanks to André for catching that!

Here’s a board. Can you figure out what itthis board says? (Encoded with the same logic as above — full 9×9 grid used for an 8-character message.)

* . . 1 1 1 . . .
. . . 1 * 2 1 . .
. * . 1 2 * 2 1 .
. . . . 1 2 * 2 1
. . . . . 1 2 * 1
. . . . . . 1 2 *
. . . . . . . 1 2
. . . . . . . . 1
. . . . . . . . .

Ignore the numbers. Just read * as 1 and . as 0, left to right, and see what secret lies beneath.

Huge thanks to André for spotting the mismatched example and helping clarify the role of the numbers — great feedback!

function textToBinary(text) {
  return text.split('')
             .map(c => c.charCodeAt(0).toString(2).padStart(8, '0'))
             .join('');
}

function binaryToGrid(binary, size = 9) {
  const grid = Array.from({ length: size }, () => Array(size).fill('.'));
  for (let i = 0; i < Math.min(binary.length, size * size); i++) {
    const row = Math.floor(i / size);
    const col = i % size;
    grid[row][col] = binary[i] === '1' ? '*' : '.';
  }
  return grid;
}

function computeHints(grid) {
  const size = grid.length;
  const result = grid.map(row => row.slice());

  const directions = [-1, 0, 1];
  for (let r = 0; r < size; r++) {
    for (let c = 0; c < size; c++) {
      if (grid[r][c] === '*') continue;
      let count = 0;
      for (let dr of directions) {
        for (let dc of directions) {
          if (dr === 0 && dc === 0) continue;
          const nr = r + dr, nc = c + dc;
          if (nr >= 0 && nr < size && nc >= 0 && nc < size && grid[nr][nc] === '*') {
            count++;
          }
        }
      }
      if (count > 0) result[r][c] = count.toString();
    }
  }
  return result;
}

function renderGrid(grid) {
  return grid.map(row => row.join(' ')).join('\n');
}

// Example
const message = "TREASURE";
const binary = textToBinary(message);
const mines = binaryToGrid(binary, 9);
const hintGrid = computeHints(mines);

console.log("Minesweeper encoding of:", message, "\n");
console.log(renderGrid(hintGrid));

When I run the script with the message "TREASURE", I get:

Minesweeper encoding of: TREASURE

* . . 1 1 1 . . .
. . . 1 * 2 1 . .
. * . 1 2 * 2 1 .
. . . . 1 2 * 2 1
. . . . . 1 2 * 1
. . . . . . 1 2 *
. . . . . . . 1 2

To decode:

  • Read each cell left to right, top to bottom.
  • * = 1, . = 0. Ignore the numbers.
  • Recombine the bits into 8-bit characters.

The result? "TREASURE".

Here’s a board. Can you figure out what it says?

* . . 1 1 1 . . .
. . . 1 * 2 1 . .
. * . 1 2 * 2 1 .
. . . . 1 2 * 2 1
. . . . . 1 2 * 1

Ignore the numbers. Just read * as 1 and . as 0, left to right, and see what secret lies beneath.

Note: The numbers on the board are generated just like in a real Minesweeper game, but they’re purely cosmetic — they don’t encode any part of the message. The real data is entirely in the pattern of mines (*) and empty cells (.), read left to right, row by row.

function textToBinary(text) {
  return text
    .split('')
    .map(c => c.charCodeAt(0).toString(2).padStart(8, '0'))
    .join('');
}

function binaryToGrid(binary, size = 9) {
  const maxBits = size * size;
  binary = binary.slice(0, maxBits);
  const grid = Array.from({ length: size }, () => Array(size).fill('.'));
  for (let i = 0; i < binary.length; i++) {
    const row = Math.floor(i / size);
    const col = i % size;
    grid[row][col] = binary[i] === '1' ? '*' : '.';
  }
  return grid;
}

function computeHints(grid) {
  const size = grid.length;
  const result = grid.map(row => row.slice());

  const dirs = [-1, 0, 1];
  for (let r = 0; r < size; r++) {
    for (let c = 0; c < size; c++) {
      if (grid[r][c] === '*') continue;
      let count = 0;
      for (let dr of dirs) {
        for (let dc of dirs) {
          if (dr === 0 && dc === 0) continue;
          const nr = r + dr, nc = c + dc;
          if (nr >= 0 && nr < size && nc >= 0 && nc < size && grid[nr][nc] === '*') {
            count++;
          }
        }
      }
      if (count > 0) result[r][c] = count.toString();
    }
  }
  return result;
}

function renderGrid(grid) {
  return grid.map(row => row.join(' ')).join('\n');
}

//example
const message = "TREASURE"; 
const binary = textToBinary(message);
const mines = binaryToGrid(binary, 9); 
const hintGrid = computeHints(mines);

console.log("Minesweeper encoding of:", message, "\n");
console.log(renderGrid(hintGrid)); 

Live demo and console output are now synchronized: both use the same bit padding and encoding logic, ensuring that examples match exactly. Thanks to André for catching that!

Can you figure out what this board says? (Encoded with the same logic as above — full 9×9 grid used for an 8-character message.)

* . . 1 1 1 . . .
. . . 1 * 2 1 . .
. * . 1 2 * 2 1 .
. . . . 1 2 * 2 1
. . . . . 1 2 * 1
. . . . . . 1 2 *
. . . . . . . 1 2
. . . . . . . . 1
. . . . . . . . .

Ignore the numbers. Just read * as 1 and . as 0, left to right, and see what secret lies beneath.

Huge thanks to André for spotting the mismatched example and helping clarify the role of the numbers — great feedback!

Source Link
Ale_Bianco
  • 665
  • 2
  • 9
  • 26

Mines of Moria — A Minesweeper Cipher 🧨

Concept

Mines of Moria is a custom cipher that hides messages inside a fake Minesweeper board. Instead of using more common games like chess or tic-tac-toe, this cipher takes advantage of Minesweeper's grid format and binary simplicity.

The idea is simple but effective:

  • Each character in your message is converted into binary (8 bits).
  • The bits are written row-by-row into the Minesweeper grid.
  • A * (mine) represents a binary 1; a . (empty) represents a 0.
  • After placing the mines, we auto-calculate the surrounding hint numbers (just like a real Minesweeper game).

To decode the message, one just has to read the board, extract the mines as bits, and rebuild the original string from binary.

The result looks like a regular Minesweeper board, but only those who know where to look will find the secret message.

Try it live: Mines of Moria Live Demo on CodePen


Why I Chose This

I wanted to move away from the usual suspects like chess boards or tic-tac-toe grids and build a cipher that:

  • Looks like something anyone might see in a retro puzzle game.
  • Is easy to generate and decode.
  • Could plausibly hide in plain sight.

It also gave me an excuse to program a basic Minesweeper renderer from scratch!


🧾 Code (JavaScript)

function textToBinary(text) {
  return text.split('')
             .map(c => c.charCodeAt(0).toString(2).padStart(8, '0'))
             .join('');
}

function binaryToGrid(binary, size = 9) {
  const grid = Array.from({ length: size }, () => Array(size).fill('.'));
  for (let i = 0; i < Math.min(binary.length, size * size); i++) {
    const row = Math.floor(i / size);
    const col = i % size;
    grid[row][col] = binary[i] === '1' ? '*' : '.';
  }
  return grid;
}

function computeHints(grid) {
  const size = grid.length;
  const result = grid.map(row => row.slice());

  const directions = [-1, 0, 1];
  for (let r = 0; r < size; r++) {
    for (let c = 0; c < size; c++) {
      if (grid[r][c] === '*') continue;
      let count = 0;
      for (let dr of directions) {
        for (let dc of directions) {
          if (dr === 0 && dc === 0) continue;
          const nr = r + dr, nc = c + dc;
          if (nr >= 0 && nr < size && nc >= 0 && nc < size && grid[nr][nc] === '*') {
            count++;
          }
        }
      }
      if (count > 0) result[r][c] = count.toString();
    }
  }
  return result;
}

function renderGrid(grid) {
  return grid.map(row => row.join(' ')).join('\n');
}

// Example
const message = "TREASURE";
const binary = textToBinary(message);
const mines = binaryToGrid(binary, 9);
const hintGrid = computeHints(mines);

console.log("Minesweeper encoding of:", message, "\n");
console.log(renderGrid(hintGrid));

Example in Action

When I run the script with the message "TREASURE", I get:

Minesweeper encoding of: TREASURE

* . . 1 1 1 . . .
. . . 1 * 2 1 . .
. * . 1 2 * 2 1 .
. . . . 1 2 * 2 1
. . . . . 1 2 * 1
. . . . . . 1 2 *
. . . . . . . 1 2

To decode:

  • Read each cell left to right, top to bottom.
  • * = 1, . = 0. Ignore the numbers.
  • Recombine the bits into 8-bit characters.

The result? "TREASURE".


How to Run

  1. Save the code in a file, e.g. minesofmoria.js

  2. Make sure you have Node.js installed

  3. Run:

    node minesofmoria.js
    

Feel free to modify the message variable with your own secret!


AI Usage Disclosure

This submission was written entirely by me. The cipher design, all code, and example were created without the use of AI tools. I used ChatGPT after completing the project to help me format and polish this writeup — but not for any code generation, debugging, or problem solving.


What I Learned

  • Designing your own cipher is genuinely fun, especially when it takes an unusual direction.
  • Minesweeper is a great canvas for hiding binary data.
  • Keeping the board visually coherent (generating the numbers) made it more believable and satisfying.

Bonus Challenge

Here’s a board. Can you figure out what it says?

* . . 1 1 1 . . .
. . . 1 * 2 1 . .
. * . 1 2 * 2 1 .
. . . . 1 2 * 2 1
. . . . . 1 2 * 1

Ignore the numbers. Just read * as 1 and . as 0, left to right, and see what secret lies beneath.