You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native井字棋开发:history状态数组访问与回退功能实现问题求助

我帮你排查了代码里的几个关键问题,这些都是导致你无法正确回退到历史棋局步骤的原因,咱们一步步来解决:

1. 历史记录(history)的格式不统一

你初始的history是一个二维数组,但在onTilePress里往history里添加的是{arr}对象,这就导致数组里的元素类型混乱,后续读取的时候自然拿不到正确的棋局状态。另外,你直接用slice()拷贝二维数组是浅拷贝,修改内层数组会污染历史记录。

修复方案:

  • 初始化history时,把初始棋局和当前玩家状态包装成对象存入数组,保持所有元素格式一致;
  • 更新history时,对棋局状态做深拷贝,避免引用污染;
  • 合并多次setState调用,避免异步更新导致的状态不一致。

2. onGoPress函数的语法与逻辑错误

你的onGoPress不仅有语法错误(setState的参数必须是对象),还没有接收要回退的步骤索引,而且按钮绑定的时候直接执行了函数,不是点击触发。

修复方案:

  • 让onGoPress接收步骤索引参数,从history中取出对应的完整状态(棋局+当前玩家);
  • 按钮绑定用箭头函数包裹,确保点击时才执行函数。

3. 按钮绑定错误

你后面的“回退步骤”按钮都错误绑定了onNewGamePress,应该统一绑定onGoPress。

完整修改后的代码

import React, { Component } from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Alert, Button, ScrollView } from 'react-native';
import { MaterialCommunityIcons as Icon } from 'react-native-vector-icons'

export default class App extends React.Component {
  constructor(props) {
    super(props);
    const initialGameState = [
      [0, 0, 0],
      [0, 0, 0],
      [0, 0, 0]
    ];
    this.state = {
      gameState: initialGameState,
      currentPlayer: 1,
      history: [
        {
          gameState: initialGameState,
          currentPlayer: 1
        }
      ],
    };
  }

  componentDidMount() {
    this.initializeGame();
  }

  initializeGame = () => {
    const initialGameState = [
      [0, 0, 0],
      [0, 0, 0],
      [0, 0, 0]
    ];
    this.setState({
      gameState: initialGameState,
      currentPlayer: 1,
      history: [{ gameState: initialGameState, currentPlayer: 1 }]
    });
  }

  onGoPress = (index) => {
    // 做边界判断,防止索引越界
    if (index < 0 || index >= this.state.history.length) {
      Alert.alert("Invalid move index!");
      return;
    }
    const targetState = this.state.history[index];
    this.setState({
      gameState: targetState.gameState,
      currentPlayer: targetState.currentPlayer
    });
  }

  onNewGamePress = () => {
    this.initializeGame();
  }

  getWinner = () => {
    const num_tiles = 3;
    const arr = this.state.gameState;
    let sum;

    // 检查行
    for (let i = 0; i < num_tiles; i++) {
      sum = arr[i][0] + arr[i][1] + arr[i][2];
      if (sum === 3) return 1;
      else if (sum === -3) return -1;
    }

    // 检查列
    for (let i = 0; i < num_tiles; i++) {
      sum = arr[0][i] + arr[1][i] + arr[2][i];
      if (sum === 3) return 1;
      else if (sum === -3) return -1;
    }

    // 检查对角线
    sum = arr[0][0] + arr[1][1] + arr[2][2];
    if (sum === 3) return 1;
    else if (sum === -3) return -1;

    sum = arr[0][2] + arr[1][1] + arr[2][0];
    if (sum === 3) return 1;
    else if (sum === -3) return -1;

    return 0;
  }

  onTilePress = (row, col) => {
    const currentPlayer = this.state.currentPlayer;
    const value = this.state.gameState[row][col];

    // 如果格子已被占用,直接返回
    if (value !== 0) return;

    // 深拷贝当前棋局状态,避免污染历史记录
    const newGameState = this.state.gameState.map(row => [...row]);
    newGameState[row][col] = currentPlayer;
    const nextPlayer = currentPlayer === 1 ? -1 : 1;

    this.setState(prevState => ({
      gameState: newGameState,
      currentPlayer: nextPlayer,
      // 将新的棋局状态和玩家状态存入历史记录
      history: [...prevState.history, { gameState: newGameState, currentPlayer: nextPlayer }]
    }), () => {
      // 在setState完成后检查胜负
      const winner = this.getWinner();
      if (winner === 1) {
        Alert.alert("Player X is the winner");
        this.initializeGame();
      } else if (winner === -1) {
        Alert.alert("Player O is the winner");
        this.initializeGame();
      }
    });
  }

  renderIcon = (row, col) => {
    const value = this.state.gameState[row][col];
    switch (value) {
      case 1: return <Icon name="close" style={styles.tileX} />
      case -1: return <Icon name="circle-outline" style={styles.tileO} />
      default: return <View />
    }
  }

  render() {
    return (
      <ScrollView style={{ marginTop: 50 }}>
        <View style={styles.container}>
          <Text style={{ fontSize: 60, fontStyle: 'italic', paddingBottom: 30 }}>TIC TAC TOE</Text>
          <View style={{ flexDirection: 'row' }} >
            <TouchableOpacity onPress={() => this.onTilePress(0, 0)} style={styles.tile} >
              {this.renderIcon(0, 0)}
            </TouchableOpacity>
            <TouchableOpacity onPress={() => this.onTilePress(0, 1)} style={styles.tile} >
              {this.renderIcon(0, 1)}
            </TouchableOpacity>
            <TouchableOpacity onPress={() => this.onTilePress(0, 2)} style={styles.tile} >
              {this.renderIcon(0, 2)}
            </TouchableOpacity>
          </View>
          <View style={{ flexDirection: 'row' }} >
            <TouchableOpacity onPress={() => this.onTilePress(1, 0)} style={styles.tile} >
              {this.renderIcon(1, 0)}
            </TouchableOpacity>
            <TouchableOpacity onPress={() => this.onTilePress(1, 1)} style={styles.tile} >
              {this.renderIcon(1, 1)}
            </TouchableOpacity>
            <TouchableOpacity onPress={() => this.onTilePress(1, 2)} style={styles.tile} >
              {this.renderIcon(1, 2)}
            </TouchableOpacity>
          </View>
          <View style={{ flexDirection: 'row' }} >
            <TouchableOpacity onPress={() => this.onTilePress(2, 0)} style={styles.tile} >
              {this.renderIcon(2, 0)}
            </TouchableOpacity>
            <TouchableOpacity onPress={() => this.onTilePress(2, 1)} style={styles.tile} >
              {this.renderIcon(2, 1)}
            </TouchableOpacity>
            <TouchableOpacity onPress={() => this.onTilePress(2, 2)} style={styles.tile} >
              {this.renderIcon(2, 2)}
            </TouchableOpacity>
          </View>
          <View style={{ paddingTop: 80 }}></View>
          <View style={styles.button}><Button title="NEW GAME " onPress={this.onNewGamePress} /></View>
          <View style={styles.button}><Button title="Go to move no: 1" onPress={() => this.onGoPress(1)} /></View>
          <View style={styles.button}><Button title="Go to move no: 2" onPress={() => this.onGoPress(2)} /></View>
          <View style={styles.button}><Button title="Go to move no: 3" onPress={() => this.onGoPress(3)} /></View>
          <View style={styles.button}><Button title="Go to move no: 4" onPress={() => this.onGoPress(4)} /></View>
          <View style={styles.button}><Button title="Go to move no: 5" onPress={() => this.onGoPress(5)} /></View>
          <View style={styles.button}><Button title="Go to move no: 6" onPress={() => this.onGoPress(6)} /></View>
          <View style={styles.button}><Button title="Go to move no: 7" onPress={() => this.onGoPress(7)} /></View>
          <View style={styles.button}><Button title="Go to move no: 8" onPress={() => this.onGoPress(8)} /></View>
          <View style={styles.button}><Button title="Go to move no: 9" onPress={() => this.onGoPress(9)} /></View>
        </View>
      </ScrollView>
    );
  }
}

// 自定义样式,可根据需求调整
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center'
  },
  tile: {
    borderWidth: 2,
    width: 100,
    height: 100,
    alignItems: 'center',
    justifyContent: 'center'
  },
  tileX: {
    fontSize: 60,
    color: '#ff3b30'
  },
  tileO: {
    fontSize: 60,
    color: '#007aff'
  },
  button: {
    marginVertical: 5,
    width: 200
  }
});

额外说明

  • 我添加了深拷贝棋局的逻辑,确保历史记录不会被后续修改污染;
  • 在setState的回调里检查胜负,保证拿到的是最新的棋局状态;
  • 给onGoPress加了边界判断,防止用户点击不存在的步骤;
  • 补充了缺失的styles样式,你可以根据自己的需求调整。

内容的提问来源于stack exchange,提问作者Sachidananda MG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 16:47:39