123

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
                  <svg width="0" height="0" display="none">
      <symbol id="flagBlack" viewBox="0 0 24 24">
          <path id="flag" d="M 6,4 V 20 A 1,1 0 0 0 8,20 V 13.6992 L 18.3418,9.93945 A 1,1 0 0 0 18.3418,8.06055 L 8,4.30078 V 4 A 1,1 0 0 0 6,4 Z" />
      </symbol>
      <symbol id="flagBlue" viewBox="0 0 24 24">
        <use xlink:href="#flag" />
      </symbol>
      <symbol id="bombBlack" viewBox="48 0 24 24">
        <path id="bomb" d="M 59,6.08594 A 6,6 0 0 0 55.377,8.17773 L 53.5723,7.13477 A 1,1 0 0 0 52.5723,8.86523 L 54.3828,9.91016 A 6,6 0 0 0 54.3809,14.0898 L 52.5723,15.1348 A 1,1 0 0 0 53.5723,16.8652 L 55.3809,15.8203 A 6,6 0 0 0 59,17.9102 V 20 A 1,1 0 0 0 61,20 V 17.9141 A 6,6 0 0 0 64.623,15.8223 L 66.4277,16.8652 A 1,1 0 0 0 67.4277,15.1348 L 65.6172,14.0898 A 6,6 0 0 0 65.6191,9.91016 L 67.4277,8.86523 A 1,1 0 0 0 66.4277,7.13477 L 64.6191,8.17969 A 6,6 0 0 0 61,6.08984 V 4 A 1,1 0 0 0 59,4 Z" />
      </symbol>
      <symbol id="bombRed" viewBox="48 0 24 24">
        <use xlink:href="#bomb" />
      </symbol>
      <symbol id="one" viewBox="0 24 24 24">
        <path d="M 13.1043,28.964 8.4403,30.174 C 7.5603,30.394 7.2743,30.658 7.2743,31.296 7.2743,31.934 7.7143,32.44 8.2863,32.44 8.5063,32.44 8.5723,32.418 8.9903,32.308 L 10.9043,31.802 V 40.8 H 8.6823 C 8.1103,40.8 7.9563,40.822 7.7583,40.954 7.4283,41.152 7.2303,41.526 7.2303,41.9 7.2303,42.23 7.4063,42.582 7.6923,42.78 7.8683,42.934 8.1763,43 8.6823,43 H 15.3263 C 15.7663,43 16.0963,42.934 16.2503,42.846 16.5803,42.626 16.7783,42.274 16.7783,41.9 16.7783,41.548 16.6023,41.218 16.3163,40.998 16.0963,40.844 15.9203,40.8 15.3263,40.8 H 13.1043 Z" />
      </symbol>
      <symbol id="two" viewBox="24 24 24 24">
        <path d="M 33.9583,40.8 C 35.8943,39.128 37.1703,38.028 37.7643,37.478 39.9423,35.498 40.4703,34.662 40.4703,33.232 40.4703,30.79 38.4683,28.964 35.7843,28.964 34.4863,28.964 33.2323,29.404 32.3523,30.196 31.6043,30.878 31.0323,31.956 31.0323,32.66 31.0323,33.232 31.5383,33.716 32.1323,33.716 32.5943,33.716 33.0123,33.452 33.1223,33.078 33.3643,32.374 33.3863,32.33 33.6063,32.066 34.0903,31.494 34.8603,31.164 35.7403,31.164 37.1703,31.164 38.2703,32.022 38.2703,33.144 38.2703,33.892 37.8963,34.376 35.6963,36.378 34.7063,37.28 34.2883,37.632 30.5923,40.69 V 43 H 40.5803 V 41.064 C 40.5803,40.536 40.5363,40.338 40.4263,40.14 40.2283,39.81 39.8543,39.612 39.4803,39.612 38.7983,39.612 38.4463,40.008 38.3803,40.8 Z" />
      </symbol>
      <symbol id="three" viewBox="48 24 24 24">
        <path d="M 59.4763,34.574 C 58.8163,34.574 58.3323,35.036 58.3323,35.674 58.3323,36.268 58.7503,36.642 59.4763,36.686 60.4223,36.73 60.7083,36.774 61.1703,36.972 62.2043,37.39 62.8423,38.16 62.8423,38.974 62.8423,39.48 62.6003,40.03 62.2263,40.382 61.6323,40.91 60.8183,41.13 59.3003,41.13 57.8923,41.13 57.2323,40.998 56.6603,40.624 56.3083,40.404 56.2203,40.36 55.9563,40.36 55.3403,40.36 54.8563,40.844 54.8563,41.46 54.8563,42.582 56.6603,43.33 59.3663,43.33 61.2803,43.33 62.4463,43.022 63.4143,42.296 64.4263,41.504 65.0423,40.272 65.0423,38.996 65.0423,37.588 64.2283,36.488 62.5563,35.674 63.9863,34.816 64.5583,34.002 64.5583,32.792 64.5583,31.89 64.1623,30.922 63.4583,30.24 62.6223,29.382 61.4783,28.964 60.0263,28.964 57.6723,28.964 55.4943,30.13 55.4943,31.406 55.4943,32 55.9783,32.506 56.5723,32.506 56.8803,32.506 57.1663,32.374 57.3643,32.176 58.0683,31.406 58.6843,31.164 59.8943,31.164 61.4123,31.164 62.3583,31.802 62.3583,32.836 62.3583,33.76 61.4343,34.574 60.3563,34.574 Z" />
      </symbol>
      <symbol id="four" viewBox="72 24 24 24">
        <path d="M 87.2383,29.316 H 84.3563 L 79.0543,38.138 V 39.964 H 85.0383 V 40.8 H 84.2023 C 83.6743,40.8 83.4763,40.822 83.2783,40.954 82.9483,41.152 82.7503,41.526 82.7503,41.9 82.7503,42.23 82.9263,42.582 83.2123,42.78 83.3883,42.934 83.6963,43 84.2023,43 H 87.1063 C 87.5683,43 87.8983,42.934 88.0523,42.846 88.3823,42.626 88.5803,42.274 88.5803,41.9 88.5803,41.196 88.0963,40.8 87.2383,40.8 V 39.964 C 88.1183,39.942 88.5803,39.568 88.5803,38.864 88.5803,38.16 88.0963,37.764 87.2383,37.764 V 29.316 M 85.0383,37.764 H 81.7383 L 85.0383,32.44 Z" />
      </symbol>
      <symbol id="five" viewBox="0 48 24 24">
        <path d="M 10.2223,55.516 H 14.6663 C 15.1283,55.516 15.4363,55.472 15.5903,55.362 15.9203,55.164 16.1183,54.79 16.1183,54.416 16.1183,54.064 15.9423,53.734 15.6783,53.514 15.4363,53.36 15.2603,53.316 14.6663,53.316 H 8.0223 V 59.564 C 8.0223,60.268 8.4843,60.796 9.1223,60.796 9.3203,60.796 9.4523,60.752 9.7383,60.62 10.6843,60.136 11.6523,59.872 12.3783,59.872 13.8523,59.872 14.8423,60.95 14.8423,62.578 14.8423,64.316 13.7863,65.13 11.4983,65.13 10.0683,65.13 9.4083,64.954 8.7483,64.404 8.4183,64.14 8.2643,64.074 7.9563,64.074 7.3623,64.074 6.8563,64.558 6.8563,65.152 6.8563,66.384 8.8803,67.33 11.5203,67.33 13.5883,67.33 14.8643,66.89 15.8323,65.834 16.6243,64.976 17.0423,63.832 17.0423,62.556 17.0423,59.74 15.0623,57.672 12.4003,57.672 11.7623,57.672 11.0143,57.804 10.2223,58.046 Z" />
      </symbol>
      <symbol id="six" viewBox="24 48 24 24">
        <path d="M 33.9803,59.872 C 34.4863,57.166 36.5103,55.23 38.8423,55.23 39.0403,55.23 39.1503,55.252 39.3923,55.34 39.7443,55.472 39.9643,55.516 40.1623,55.516 40.7783,55.516 41.2843,55.01 41.2843,54.394 41.2843,53.536 40.2503,52.964 38.7103,52.964 36.6863,52.964 34.8823,53.888 33.4083,55.692 32.2863,57.1 31.7143,58.772 31.7143,60.73 31.7143,62.314 32.1103,64.03 32.6823,65.086 33.5183,66.582 34.8603,67.33 36.6863,67.33 38.1603,67.33 39.2383,66.912 40.0303,66.032 40.7783,65.218 41.1743,64.096 41.1743,62.842 41.1743,60.356 39.3043,58.332 36.9943,58.332 35.8723,58.332 35.0363,58.75 33.9803,59.872 M 34.2003,62.402 C 34.7943,61.324 35.8943,60.532 36.8403,60.532 38.0283,60.532 38.9743,61.544 38.9743,62.82 38.9743,64.25 38.1383,65.13 36.7523,65.13 35.3223,65.13 34.5303,64.316 34.2003,62.402 Z" />
      </symbol>
      <symbol id="seven" viewBox="48 48 24 24">
        <path d="M 62.2703,55.516 58.9703,65.218 C 58.8603,65.592 58.8163,65.768 58.8163,65.966 58.8163,66.538 59.3443,67.022 59.9383,67.022 60.5103,67.022 60.7963,66.736 61.0823,65.944 L 64.5803,55.494 V 53.316 H 55.0543 V 55.252 C 55.0543,55.78 55.0983,55.978 55.2083,56.176 55.4063,56.506 55.7803,56.704 56.1543,56.704 56.8363,56.704 57.1883,56.308 57.2543,55.516 Z" />
      </symbol>
      <symbolid="eight" viewBox="72 48 24 24">
        <path d="M 86.6443,59.982 C 87.9643,59.102 88.5803,58.156 88.5803,56.946 88.5803,54.746 86.5343,52.964 84.0043,52.964 81.4523,52.964 79.4283,54.746 79.4283,56.99 79.4283,58.156 80.0443,59.102 81.3643,59.982 79.9563,60.862 79.2303,62.006 79.2303,63.37 79.2303,65.724 81.1663,67.33 83.9823,67.33 86.8423,67.33 88.7783,65.724 88.7783,63.37 88.7783,62.006 88.0523,60.862 86.6443,59.982 M 84.0043,55.164 C 85.4123,55.164 86.3803,55.956 86.3803,57.1 86.3803,58.2 85.3903,58.992 84.0043,58.992 82.6183,58.992 81.6283,58.2 81.6283,57.078 81.6283,55.956 82.5963,55.164 84.0043,55.164 M 84.0043,61.214 C 85.4563,61.214 86.5783,62.094 86.5783,63.216 86.5783,64.316 85.5003,65.13 84.0043,65.13 82.5083,65.13 81.4303,64.316 81.4303,63.194 81.4303,62.094 82.5963,61.214 84.0043,61.214 Z" />
      </symbol>
    </svg>
    <div id="gamepad"></div>

            
          
!
            
              #flagBlack, #bombBlack, #eight {
    fill: #000;
}
#flagBlue, #one {
    fill: #0000ff;
}
#bombRed {
    fill: #cc0000;
}
#two {
    fill: #00aa44;
}
#three {
    fill: #d45500;
}
#four {
    fill: #aa0000;
}
#five {
    fill: #808000;
}
#six {
    fill: #0088aa;
}
#seven {
    fill: #aa0088;
}

body {
    font-family: sans-serif;
    font-size: 13px;
}

#gamepad {
    display: flex;
    flex-direction: column;
    width: 900px;
    align-items: center;
}

.selector {
    display: flex;
    justify-content: center;
    margin: 0 0 2em;
    padding: 0;
    list-style-type: none;
    color: black;
    font-weight: 700;
}
.selector li {
    display: block;
    padding: 0.6em 1em;
    margin: 0 0.5em;
    border: 1px solid #888;
    border-radius: 4px;
    cursor: pointer;
}
.selector li:hover {
    background: #00208840;
}
.selector li:active {
    background: #202088dd;
    color: white;
}

.result {
    margin: 0 0 2em;
}
.result label {
    display: inline-block;
    width: 6em;
    text-align: right;
}

.result span {
    display: inline-block;
    padding: 0.3em;
    margin: 0 0 0 1em;
    text-align: center;
    width: 4em;
    height: 1.4em;
    line-height: 1.4em;
    border: 1px solid #888;
    border-radius: 4px;
}

.result.won span {
    background-color: #88cc88;
}
.result.lost span {
    background-color: #dd7777;
}

.bg {
    fill: #eee;
    pointer-events: none;
}
.under {
    opacity: 0;
}
.field.hidden {
    fill: #ccc;
}
svg:not(.ended) .under.active:hover + .field.hidden {
    fill: #ddd;
}
.field.shown {
    fill: #fff;
}
.ended .field.hidden {
    fill: #888;
}
.ended .field.wrong {
    fill: #dd7777;
}
.mark, .field {
    pointer-events: none;
}
            
          
!
            
              const base = {
    WAITING: 0,
    RUNING:  1,
    WON: 2,
    LOST: 3,

    ends: [null, null, "won", "lost"],
  
    UNMARKED:  0,
    BLACKMARK: 1,
    BLUEMARK:  2,

    marks: [null, "flagBlack", "flagBlue"],

    HIDDEN:   0,
    SOLVED:   1,
    EXPLODED: 2,

    buttons: ["hidden", "shown", "wrong"],
    bombs: ["bombBlack", "bombRed"],

    numbers: [
        null,
        "one",
        "two",
        "three",
        "four",
        "five",
        "six",
        "seven",
        "eight"
    ],

    fieldsize: 30,
    
    levels: {
        'small': {
            desc: 'Small (8×8)',
            width: 8,
            height: 8,
            quota: 10
        },
        'mid': {
            desc: 'Mid (16×16)',
            width: 16,
            height: 16,
            quota: 40
        },
        'large': {
            desc: 'Large (30×16)',
            width: 30,
            height: 16,
            quota: 99
        }
    }
}

class Game extends React.Component {
    constructor (props) {
        super(props);

        this.state = {
            ...base.levels.small,
            found: 0,
            mid: false,
            run: base.WAITING
        };
    }

    selectSize (level) {
        this.setState({
            ...level,
            found: 0,
            run: base.WAITING
        });
    }

    onMarked (count) {
        this.setState({found: this.state.found + count});
    }

    onRun (run) {
        this.setState({run: run});
    }

    render () {
        return <React.Fragment>
            <Selector
                selectSize = { this.selectSize.bind(this) }
            />
           <p
                className={ "result " + base.ends[this.state.run] || "" }
            ><label>Marked:</label><span>{
                    this.state.found} / { this.state.quota
                }</span><Clock run = { this.state.run } />
            </p>
            <Area
                onRun = { this.onRun.bind(this) }
                onMarked = { this.onMarked.bind(this) }
                { ...this.state }
            />
        </React.Fragment>
    }
}

class Clock extends React.Component {
    constructor (props) {
        super(props);

        this.state = {
            dur: 0
        };
    }

    start () {
        const time = Date.now();
        this.timerID = setInterval(() => {
            const elapsed = Math.floor((Date.now() - time) / 1000);
            this.setState({dur: elapsed});
        }, 100);
    }

    stop () {
        clearInterval(this.timerID);
    }

    componentDidUpdate(prevProps) {
        if (this.props.run === prevProps.run) return;
        switch (this.props.run) {
        case base.WAITING:
            this.stop();
            return this.setState({dur: 0});
        case base.RUNING:
            return this.start();
        case base.WON:
        case base.LOST:
            return this.stop();
        }
    }

    componentWillUnmount () {
        this.stop();
    }

    formatDuration (dur) {
        return Math.floor(dur / 60) + ':' +
            ((dur % 60) / 100).toFixed(2).split('.')[1]
    }

    render () {
        return <React.Fragment>
            <label>Timer:</label><span>{ this.formatDuration(this.state.dur) }</span>
        </React.Fragment>;
    }
}

class Selector extends React.Component {
    render () {
        return <ul className = "selector">
            { Object.entries(base.levels).map(([key, level]) => <li
                key = { key }
                onClick = { () => this.props.selectSize(level) }
            >{ level.desc }</li>) }
        </ul>
    }
}

function seedFields (props) {
    const {width, height, quota} = props;
    const total = width * height;

    // seed field data array
    const fields = new Array(total).fill(0).map((v, i) => {
        const col = i % width,
              line = Math.floor(i / width);
        return {
            col, line,
            neighbours: getNeighbours(col, line, props),
            full: false,
            marked: base.UNMARKED,
            revealed: base.HIDDEN,
            highlight: false
        }
    });

    // randomly position bombs
    let bombs = quota;
    while (bombs) {
        const place = Math.floor(Math.random() * total);
        if (fields[place].full) continue;
        fields[place].full = true;
        bombs--;
    }

    // provide hints
    for (const field of fields) {
        field.hint = field.neighbours.reduce((sum, idx) => {
            return sum + fields[idx].full;
        }, 0);
    }

    return fields;
}

function getNeighbours (x, y, props) {
    const n = [];
    for (let i of [-1, 0, 1]) {
        const nx = x + i;
        for (let j of [-1, 0, 1]) {
            const ny = y + j;
            if (nx >= 0 && nx < props.width &&
                ny >= 0 && ny < props.height) {
                    n.push(nx + ny * props.width);
            }
        }
    }
    return n;
}

class Area extends React.Component {
    constructor (props) {
        super(props);

        this.state = {
            fields: seedFields(props)
        };
    }

    static getDerivedStateFromProps(props) {
        if (props.run === base.WAITING) {
            return {
                fields: seedFields(props)
            };
        }
        return null;
    }

    loopNeighbours (field, callback) {
        for (const idx of field.neighbours) {
            callback.bind(this)(this.state.fields[idx]);
        };
    }

    highlight (field) {
        field.highlight = true;
    }

    reveal (field) {
        if (field.revealed || field.marked) return;

        if (field.full) {
            field.revealed = base.EXPLODED;
            this.props.onRun(base.LOST);
        } else {
            field.revealed = base.SOLVED;

            if (!field.hint) {
                this.loopNeighbours(field, this.reveal);
            }
        }
    }

    cautiousHighlight (field) {
        if (!field.revealed) return;

        this.loopNeighbours(field, this.highlight);
    }

    cautiousReveal (field) {
        if (!field.revealed) return;

        const marks = field.neighbours.filter(idx => this.state.fields[idx].marked).length;
        if (marks === field.hint) {
            this.loopNeighbours(field, this.reveal);
        }
    }

    mark (field) {
        const choice = this.props.mid ? 3 : 2;
        if (field.marked === base.UNMARKED) {
            this.props.onMarked(1);
        } else if (field.marked === base.BLACKMARK) {
            this.props.onMarked(-1);
        }
        field.marked = (field.marked + 1) % choice;
    }

    getTargetField (event) {
        const col = event.target.getAttribute('x') / base.fieldsize;
        const line = event.target.getAttribute('y') / base.fieldsize;
        return this.state.fields[line * this.props.width + col];
    }
 
    catch (e) {
        e.preventDefault();
        e.stopPropagation();
    }

    mousedown (event) {
        this.catch(event);
        if (this.props.run === base.WAITING) this.props.onRun(base.RUNING);
        if (this.props.run > base.RUNING) return;

        const field = this.getTargetField(event);

        switch (event.button) {
        case 0:
            this.highlight(field);
            break;
        case 1:
            this.cautiousHighlight(field);
            break;
        case 2:
            this.mark(field);
            break;
        }
        this.setState({fields: this.state.fields});
    }

    mouseup (event) {
        this.catch(event);
        if (this.props.run > base.RUNING) return;

        const field = this.getTargetField(event);
  
        switch (event.button) {
        case 0:
            this.reveal(field);
            break;
        case 1:
            this.cautiousReveal(field);
            break;
        }
        if (field.revealed === base.EXPLODED) return;

        const hidden = this.state.fields.filter(field => !field.revealed).length;
        if (hidden == this.props.quota) {
            this.props.onRun(base.WON);
        }

        this.state.fields.forEach(field => field.highlight = false);

        this.setState({fields: this.state.fields});
    }

    renderField (field) {
        return <Field
            { ...field}
            run = { this.props.run }
            key = { `frg-${field.col}-${field.line}` }
        />;
    }

    render () {
        const w = this.props.width * base.fieldsize,
              h = this.props.height * base.fieldsize;
        return <svg
            width = { w }
            height = { h }
            viewBox = { `0 0 ${w} ${h}` }
            className = { this.props.run > base.RUNING ? "ended" : null }
            onMouseDown = { this.mousedown.bind(this) }
            onMouseUp = { this.mouseup.bind(this) }
            onContextMenu = { this.catch.bind(this) }
        >
            <rect
                width = "100%"
                height = "100%"
                className = "bg"
            />
            { this.state.fields.map(this.renderField.bind(this)) }
        </svg>;
    }
}

class Field extends React.PureComponent {
    button () {
        if (this.props.run === base.WON || this.props.revealed ||
                (this.props.highlight && !this.props.marked)) {
            return base.buttons[1];
        } else if (this.props.run === base.LOST && this.props.marked && !this.props.full) {
            return base.buttons[2];
        } else {
            return base.buttons[0];
        }
    }
    
    symbol () {
        if (this.props.revealed) {
            return this.props.full ? base.bombs[1] : base.numbers[this.props.hint];
        } else if (this.props.marked) {
            return base.marks[this.props.marked];
        } else if ((this.props.run > base.RUNING) && this.props.full) {
            return  base.bombs[0];
        }
        return false;
    }

    render () {
        const button = this.button();
        const symbol = this.symbol();
        return [
            <rect              
                key = "under"
                x = { this.props.col * base.fieldsize }
                y = { this.props.line * base.fieldsize }
                width = { base.fieldsize }
                height = { base.fieldsize }
                className = { "under" + (symbol ? "" : " active") }
            />,
            <rect              
                key = "fld"
                x = { this.props.col * base.fieldsize + 2 }
                y = { this.props.line * base.fieldsize + 2 }
                rx = "3"
                width = { base.fieldsize - 4 }
                height = { base.fieldsize - 4 }
                className = { `field ${button}` }
            />,

            symbol && <use
                key = "sym"
                x = { this.props.col * base.fieldsize + 3 }
                y = { this.props.line * base.fieldsize + 3 }
                width = { base.fieldsize - 6 }
                height = { base.fieldsize - 6 }
                xlinkHref = { `#${symbol}` }
                className = "mark"
            />
        ];
    }
}

ReactDOM.render(<Game />, document.querySelector("#gamepad"));
            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.

Console