React生成数独

运行效果

选择难度

在这里插入图片描述

错误数字提示

在这里插入图片描述

选择数字相同数字提示

在这里插入图片描述

游戏完成提示(演示效果)

在这里插入图片描述

1.项目结构

//项目用到了Ant-design
import {message, Modal, Button, Select} from 'antd'

在这里插入图片描述

2.代码

App.js
import './App.css';
import Sudoku from "./components/sudoku/Sudoku";

function App() {
    return (
        <div className="App">
            <Sudoku/>
        </div>
    );
}

export default App;
sudoku/sudoku.jsx
import React, {Component} from "react";
import './index.css'
import {block_contains, column_contains, init, row_contains} from "./init";
import {message, Modal, Button, Select} from 'antd'

const {Option} = Select
const levels = ['入门', '简单', '中等', '困难',]
const classes = ['cellClick', 'wrongNumber', 'sameNumber']
const STANDARD_ARR = [1, 2, 3, 4, 5, 6, 7, 8, 9]
let timeInterval

function init_game(level) {
    let arr = init(level)
    for (let i = 0; i < arr.length; i++) {
        for (let j = 0; j < arr[i].length; j++) {
            arr[i][j] = {value: arr[i][j], disabled: arr[i][j] !== undefined}
        }
    }
    return arr
}

function findIndex(target, arr) {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] === target)
            return i
    }
    return undefined
}

function setError(target, error) {
    if (error) {
        target.classList.add(classes[1])
        message.error('填的数字重复了')
    } else {
        target.classList.remove(classes[1])
    }
}

function computeTime(time) {
    let sec = Math.floor(time % 60)
    let min = Math.floor(time / 60)
    return `${min} : ${sec}`
}

export default class Sudoku extends Component {
    state = {
        result: [],
        choose: {i: -1, j: -1},
        hoverTarget: undefined,
        showModal: false,
        level: 1,
        startTime: 0,
    }

    constructor(props) {
        super(props);
        this.table = React.createRef()
    }


    init() {
        this.setState({result: init_game(this.state.level), startTime: 0})
        clearInterval(timeInterval)
        timeInterval = this.newInterval()
    }

    newInterval() {
        return setInterval(() => {
            let startTime = this.state.startTime + 1
            this.setState({startTime})
        }, 1000)
    }

    componentDidMount() {
        this.init()
    }

    componentWillUnmount() {
        clearInterval(timeInterval)
    }

    setGrandsonClass(target, num, i, j) {
        let grandParent = this.table.current
        if (target) {
            for (let k = 0; k < grandParent.childNodes.length; k++) {
                for (let l = 0; l < grandParent.childNodes[k].childNodes.length; l++) {
                    let temp = grandParent.childNodes[k].childNodes[l].childNodes[0]
                    let number = temp.innerText
                    //相同数字的
                    if (number !== '' && num === number) temp.classList.add(classes[2])
                    else temp.classList.remove(classes[2])
                    // 点击的
                    if (i === k && j === l) temp.classList.add(classes[0])
                    else temp.classList.remove(classes[0])
                }
            }
        } else {
            //清空所有附加的classname
            for (let k = 0; k < grandParent.childNodes.length; k++) {
                for (let l = 0; l < grandParent.childNodes[k].childNodes.length; l++) {
                    let temp = grandParent.childNodes[k].childNodes[l].childNodes[0]
                    temp.classList.remove(...classes)
                }
            }
        }
    }

    clickEle(e) {
        if (e.target.nodeName === 'SPAN') {
            let target = e.target.parentNode, parent = target.parentNode, grandParent = parent.parentNode
            let i = findIndex(parent, grandParent.childNodes)
            let j = findIndex(target, parent.childNodes)
            this.setState({choose: {i, j}})
            this.setGrandsonClass(target, e.target.innerText, i, j)
        }
    }

    chooseNum(e) {
        let {i, j} = this.state.choose
        if (i !== -1 || j !== -1) {
            let num = Number(e.target.innerText)
            let result = this.state.result
            if (result[i][j].disabled) {
                message.warning('这个数字不可更改')
                return
            }
            let target = this.table.current.childNodes[i].childNodes[j].childNodes[0]
            if (row_contains(result[i], num) || column_contains(result, j, num) || block_contains(result, i, j, num)) {
                setError(target, true)
            } else {
                setError(target, false)
            }
            result[i][j].value = num
            this.setState({result})
            this.check()
        }
    }

    clear() {
        let result = this.state.result
        let {i, j} = this.state.choose
        if (result[i][j].value && !result[i][j].disabled) {
            result[i][j].value = undefined
            this.setState({result})
        }
    }

    check() {
        let result = this.state.result
        let hasBlank = false
        for (let i = 0; i < result.length; i++) {
            for (let j = 0; j < result[i].length; j++) {
                if (result[i][j].value === undefined) {
                    hasBlank = true
                    return
                }
            }
        }
        if (!hasBlank) {
            this.setState({showModal: true})
            clearInterval(timeInterval)
            message.success('恭喜完成!')
        }
    }

    nextGame() {
        this.setState({showModal: false})
        this.init()
        this.setGrandsonClass()
    }

    nextGameCancel() {
        this.setState({showModal: false})
        timeInterval = this.newInterval()
    }

    chooseLevel(e) {
        this.setState({level: e})
    }

    render() {

        return (<div>
            <Select placeholder="难度" onChange={this.chooseLevel.bind(this)}
                    className="chooseLevel">
                <Option value={1}>{levels[0]}</Option>
                <Option value={2}>{levels[1]}</Option>
                <Option value={3}>{levels[2]}</Option>
                <Option value={4}>{levels[3]}</Option>
            </Select>
            <Button onClick={this.clear.bind(this)} className="clickButton clear">清除</Button>
            <Button onClick={this.init.bind(this)} className="clickButton reset">重置</Button>
            <div className="clickButton timeUsed">{computeTime(this.state.startTime)}</div>
            <div className="table" ref={this.table} onClick={this.clickEle.bind(this)}>
                {this.state.result.map((row_item, row_index) => {
                    return (
                        <div className={row_index % 3 === 0 && row_index > 0 ? 'row line' : 'row'}
                             key={row_index}>
                            {row_item.map((cell_item, cell_index) => {
                                return (
                                    <div className={cell_index % 3 === 0 && cell_index > 0 ? 'cell column' : 'cell'}
                                         key={cell_index}>
                                        <span className="content"> {cell_item.value}</span>
                                    </div>)
                            })}
                        </div>)
                })}
            </div>
            <div id="numbers">
                {STANDARD_ARR.map(item => {
                    return (<span key={item} className="chooseNumber"
                                  onClick={this.chooseNum.bind(this)}>
                                {item}
                            </span>)
                })}
            </div>
            <Modal open={this.state.showModal}
                   title="游戏完成"
                   onOk={this.nextGame.bind(this)}
                   onCancel={this.nextGameCancel.bind(this)}
                   cancelText="取消"
                   okText="重新开始">
                <span>恭喜完成游戏,用时 {computeTime(this.state.startTime)}, 开始下一局吧</span>
            </Modal>
        </div>)
    }
}
sudoku/index.css
.table {
    width: 542px;
    height: 542px;
    margin: 30px auto 20px;
    border: 1px #a5a5a5 solid;
    border-radius: 4px;
}

.row {
    height: 60px;
}

.row.line {
    border-top: black 2px solid;
}

.cell.column {
    border-left: black 2px solid;
}

.cell {
    display: inline-block;
    border: 1px #a5a5a5 solid;
    border-radius: 4px;
}

.content {
    display: block;
    width: 58px;
    height: 58px;
    line-height: 58px;
    font-size: 26px;
    float: left;
}

.wrongNumber {
    background: lightcoral !important;
}

.sameNumber {
    background: lightgray;
}

.cellClick {
    background: #a5a5a5;
}

.chooseNumber {
    padding: 5px 10px;
    margin: 10px;
    font: 26px bolder 黑体;
    width: 30px;
    height: 30px;
    background: lightgray;
    border-radius: 3px;
}

.chooseNumber:hover {
    background: #a5a5a5;
}

.clickButton {
    position: fixed;
    right: 120px;
    font-size: 20px;
    padding: 5px 10px;
    color: black;
    background: #fbf9f9;
    border-radius: 8px;
    box-shadow: lightgray 4px 4px 4px;
    border: lightgray 1px solid;
    width: 100px;
    height: 40px;
}

.clickButton:hover {
    background: dodgerblue;
    color: white !important;
}

.chooseLevel {
    position: fixed;
    top: 200px;
    right: 120px;
    border-radius: 8px;
    box-shadow: lightgray 4px 4px 4px;
    border: lightgray 1px solid;
    width: 100px;
}

.reset {
    top: 100px;
}

.clear {
    top: 50px;
}

.timeUsed{
    top: 150px;
    width: 78px;
    height: 28px;
}
sudoku/init.js
function nextNumber(num) {
    num += 1;
    num = num > 9 ? 1 : num;
    return num;
}

function generateRow() {
    let arr = []
    for (let i = 0; i < 9; i++) {
        arr[i] = i + 1;
    }
    return arr
}

export const row_contains = (arr, num) => {
    if (arr == null) {
        return false;
    }
    for (let j of arr) {
        if (j && j.value === num) {
            return true;
        }
    }
    return false;
}

export const column_contains = (arr, column, num) => {
    for (let j of arr) {
        if (!j[column]) {
            return false
        }
        if (j[column].value === num) {
            return true;
        }
    }
    return false;
}

export const block_contains = (arr, i, j, num) => {
    let row = Math.floor(i / 3) * 3;
    let column = Math.floor(j / 3) * 3
    let temp = [];
    for (let k = row; k < row + 3; k++) {
        for (let l = column; l < column + 3; l++) {
            if (arr[k] && arr[k][l]) temp[(k - row) * 3 + (l - column)] = {value: arr[k][l]};
        }
    }
    return row_contains(temp, num);
}

function switchRow(arr, i, j) {
    for (let k = 0; k < arr[i].length; k++) {
        let temp = arr[i][k];
        arr[i][k] = arr[j][k];
        arr[j][k] = temp;
    }
}

function switchColumn(arr, i, j) {
    for (let k = 0; k < arr.length; k++) {
        let temp = arr[k][i];
        arr[k][i] = arr[k][j];
        arr[k][j] = temp;
    }
}

function random(seeds) {
    return Math.ceil(Math.random() * seeds)
}

function switchRowAndColumn(arr) {
    let times = random(40) + 10;
    for (let i = 0; i < times; i++) {
        {
            let j = random(3) - 1;
            let k = random(3) - 1;
            if (j !== k) switchRow(arr, j, k);
        }
        {
            let j = random(3) - 1;
            let k = random(3) - 1;
            if (j !== k) switchRow(arr, j + 3, k + 3);
        }
        {
            let j = random(3) - 1;
            let k = random(3) - 1;
            if (j !== k) switchRow(arr, j + 6, k + 6);
        }
        {
            let j = random(3) - 1;
            let k = random(3) - 1;
            if (j !== k) switchColumn(arr, j, k);
        }
        {
            let j = random(3) - 1;
            let k = random(3) - 1;
            if (j !== k) switchColumn(arr, j + 3, k + 3);
        }
        {
            let j = random(3) - 1;
            let k = random(3) - 1;
            if (j !== k) switchColumn(arr, j + 6, k + 6);
        }
    }
}

function clearCells(level, arr) {
    let min, max;

    switch (level) {
        case 1:
            min = 20;
            max = 10;
            break;
        case 2:
            min = 30;
            max = 10;
            break;
        case 3:
            min = 35;
            max = 10;
            break;
        default:
            min = 45;
            max = 5;
            break;
    }

    let count = random(max) + min
    for (let i = 0; i < count; i++) {
        do {
            let n = random(9) - 1;
            let m = random(9) - 1;
            if (arr[n][m] > 0) {
                arr[n][m] = undefined
                break;
            }
        } while (true);
    }
}

export const init = (level) => {
    let result = [];
    result[0] = generateRow();
    for (let i = 1; i < 9; i++) {
        result.push([])
        for (let j = 0; j < 9; j++) {
            let number = nextNumber(result[i - 1][j]);
            while (row_contains(result[i], number) || column_contains(result, j, number) || block_contains(result, i, j, number)) {
                number = nextNumber(number);
            }
            result[i][j] = number;
        }
    }
    switchRowAndColumn(result);
    clearCells(level, result);
    return result;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值