Hướng dẫn làm game tic tac toe c năm 2024

Tuy nhiên phần tutorial trên chỉ với game 3x3 đơn giản. Mình sẽ hướng dẫn các bạn tạo game với độ rộng 20x20 ngay sau đây.

Cùng tìm hiểu và thử làm theo nha!

Lưu ý là nên đọc, hiểu và tự viết code thay vì copy paste code nhé các bạn!

Tôi sẽ bỏ qua phần tạo ReactJS project, các bạn tìm hiểu và tự tạo bằng các câu lệnh nha!

Các bước tạo game

Như các bạn đã biết, React là một thư viện JavaScript, dùng để xây dựng giao diện người dùng một cách linh hoạt. Nó cho phép bạn soạn các giao diện người dùng phức tạp từ các đoạn mã nhỏ và biệt lập được gọi là “components”. Chúng ta sử dụng các components để khai báo cho React biết những gì hiển thị trên màn hình [buttons, forms,…] . Khi dữ liệu thay đổi, React sẽ cập nhật và re-render các components đó.

Vậy, khi chúng ta xây dựng nên Tic-Tac-Toe game, hình dung trong đầu sẽ có 3 components chính:

  • Square: là component hiển thị một button duy nhất.
  • Board: hiển thị 20 ô vuông.
  • Game: hiển thị một bảng tổng thể mà người dùng tương tác.

Khai báo các components

Đầu tiên, chúng ta khai báo component Square.jsx:

const Square = [props] => {
  return [
   props.onClick[]}
{props.value} ]; };

CSS cho square nha [App.css]:

.square {
  background: 
# fff;
  border: 1px solid 
# 999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}
.styleX {
  color: 
# 345594;
  font-weight: 700;
}
.styleO {
  color: red;
  font-weight: 700;
}
.square:focus {
  outline: none;
}

Cùng tạo component Board.jsx nào:

Vì game có độ dài lớn, nên ta cần phải biểu thị vị trí bằng [i: tương ứng với trục hoành, j: tương ứng với trục tung] nhé.

Cùng theo dõi code cùng comment nha:

const Board = [] => {
//Khai báo số ô vuông cần render. Ở đây, tôi mặc định độ rộng của game là 20, bạn cũng có thể tùy chỉnh theo ý mình nhé.
   const CONFIG = {
     DIMENSION: 20,
   };
// Set số square trong game
  const [squares, setSquares] = useState[
    Array[CONFIG.DIMENSION].fill[Array[CONFIG.DIMENSION].fill[null]]
  ];
// Định vị đến lượt X hay O 
  const [xIsNext, setXIsNext] = useState[true];
// Hàm xử lý sự kiện click vào ô
  const handleClick = [i, j] => {
  //Nếu đã có giá trị tại vị trí [i,j] tương ứng, return
    if [squares[i][j]] {
      return;
    }
   // Nếu ô còn trống, điền giá trị phụ thuộc vào lượt isNext
    let newSquares = squares.map[[r] => [...r]];
    newSquares[i][j] = xIsNext ? "X" : "O";
    // Gán giá trị cho ô chỉ định
    setSquares[newSquares];
    //Chỉ định người tiếp theo
    setXIsNext[!xIsNext];
  };
  const renderSquare = [i, j] => {
    return  handleClick[i, j]} />;
  };
   // Render
  const renderTwoDimensionSquare = [i, j] => {
  //Đảm bảo việc render không bị bug hiện thêm ô trống
    let arrForLoopRow = Array[i].fill[null];
    let arrForLoopCol = Array[j].fill[null];
    return arrForLoopRow.map[[e1, idx] => [
      
{arrForLoopCol.map[[e2, jdx] => renderSquare[idx, jdx]]}
]]; }; // Để trực quan hơn, ta sẽ thêm text để thông báo cho người chơi biết lượt đi tiếp theo là của ai: let status = "Next player: " + [xIsNext ? "X" : "O"]; //Return phần board game return [
{status}
{renderTwoDimensionSquare[CONFIG.DIMENSION, CONFIG.DIMENSION]}
]; };

Xây dựng component cha Game.jsx:

const Game = [] => {
  return [
    
]; };

CSS một chút nhé: [App.css]:

.game-board {
  margin: auto;
  margin-top: 50px;
}
.game {
  display: flex;
  flex-direction: row;
  align-items: center;
}

Giao diện hiển thị sẽ thế này:

Logic hoàn thiện trò chơi

Vậy đến khi nào thì có người chiến thắng?? Cùng nhau xây dựng logic để đi tìm người chiến thắng nhé!

Ta cần xác định rằng việc tính toán để tìm người chiến thắng sẽ cho component Board.jsx đảm nhiệm, vậy nên, trong Board.jsx, ta khai báo:

//Ban đầu chưa có người chiến thắng, mặc định là null
  const [theWinner, setTheWinner] = useState[null];
// Trong hàm handleClick, ta phải sửa một chút logic: Khi đã có người chiến thắng thì sẽ không click được nữa, và setTheWinner là người chiến thắng
// Vậy nên hàm handleClick sẽ được thay thế như sau:
  const handleClick = [i, j] => {
    if [theWinner || squares[i][j]] {
      return;
    }
    let newSquares = squares.map[[r] => [...r]];
    newSquares[i][j] = xIsNext ? "X" : "O";
    setSquares[newSquares];
    setXIsNext[!xIsNext];
    let whoTheWinner = //TODO: build calculateWinnerNew[]
    if [whoTheWinner] {
      setTheWinner[whoTheWinner];
    }
  };

Okay, giờ việc cần làm là chúng ta sẽ phải xây dựng hàm calculateWinnerNew[] để xác định người chiến thắng.

Quy tắc tìm người chiến thắng là: Người chơi nào xếp được đủ 5 ô theo chiều ngang/dọc/chéo thì người đó chiến thắng.

Vậy, trong Board.jsx, ta khai báo các hàm để tìm người chiến thắng như sau:

const isBingo = [currentPlayer, arr] => {
  let count = 0;
  for [let i = 0; i < arr.length; i++] {
    if [arr[i] == currentPlayer] {
      count++;
      if [count == 5] {
        return true;
      }
    } else {
      count = 0;
    }
  }
  return false;
};

Để dễ hình dung, tôi sẽ đặt theo các hướng: Đông [E] - Tây [W] - Nam [S] - Bắc [N] - Đông Nam [SE] - Tây Nam [SW] - Tây Bắc [NW] - Đông Bắc [NE]. Như vậy đối với mỗi vị trí được chỉ định, ta sẽ có 8 vị trí xung quanh tương ứng, biểu thị như sau:

// Nam
const directSouth = [i,j] => {
return [i+1, j]
}
// Bắc
const directNorth = [i,j] => {
return [i-1, j]
}
//Tây
const directWest = [i,j] => {
return [i, j-1]
}
//Đông
const directEast = [i,j] => {
return [i, j+1]
}
//Đông Nam
const directSouthEast = [i,j] => {
let [_i, _j] = directSouth[i,j];
return directEast[_i, _j];
}
//Tây Nam
const directSouthWest = [i,j] => {
let [_i, _j] = directSouth[i,j];
return directWest[_i, _j];
}
//Tây Bắc
const directNorthWest = [i,j] => {
let [_i, _j] = directNorth[i,j];
return directWest[_i, _j];
}
//Đông Bắc
const directNorthEast = [i,j] => {
let [_i, _j] = directNorth[i,j];
return directEast[_i, _j];
}

Sau khi xác định được vị trí các ô xung quanh 1 vị trí được chỉ định, ta cần xác định xem những ô quanh nó có giá trị giống nó hay không.

Để tìm những ô xung quanh vị trí được chỉ định, ta có hàm:

const findValueFromSquares = [squares, arrLocation] => {
return squares[arrLocation[0]][arrLocation[1]]
}

Để kiểm tra tính từ vị trí được chỉ định đến vị trí chiến thắng có bị quá lề hay không, ta có hàm:

const isValidPosition = [arrLocation] => {
return ![arrLocation[0] >= CONFIG.DIMENSION || arrLocation[1] >= CONFIG.DIMENSION || arrLocation[0] < 0 || arrLocation[1] < 0]
}

Ta sẽ thiết lập một Chain tính toán như sau, với ví dụ người chiến chơi điền đủ 5 ô theo hướng Bắc - Nam:

.square {
  background: 
# fff;
  border: 1px solid 
# 999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}
.styleX {
  color: 
# 345594;
  font-weight: 700;
}
.styleO {
  color: red;
  font-weight: 700;
}
.square:focus {
  outline: none;
}

0

Tương tự, ta sẽ build cái Chain còn lại:

  • Từ Tây sang Đông [đường thẳng]:

.square {
  background: 
# fff;
  border: 1px solid 
# 999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}
.styleX {
  color: 
# 345594;
  font-weight: 700;
}
.styleO {
  color: red;
  font-weight: 700;
}
.square:focus {
  outline: none;
}

1

  • Từ Tây Bắc - Đông Nam [đường chéo]:

.square {
  background: 
# fff;
  border: 1px solid 
# 999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}
.styleX {
  color: 
# 345594;
  font-weight: 700;
}
.styleO {
  color: red;
  font-weight: 700;
}
.square:focus {
  outline: none;
}

2

  • Từ Đông Bắc - Tây Nam [đường chéo]:

.square {
  background: 
# fff;
  border: 1px solid 
# 999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}
.styleX {
  color: 
# 345594;
  font-weight: 700;
}
.styleO {
  color: red;
  font-weight: 700;
}
.square:focus {
  outline: none;
}

3

Okay, giờ chúng ta sẽ tổng hợp lại:

.square {
  background: 
# fff;
  border: 1px solid 
# 999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}
.styleX {
  color: 
# 345594;
  font-weight: 700;
}
.styleO {
  color: red;
  font-weight: 700;
}
.square:focus {
  outline: none;
}

4

Kết quả return của hàm buildChainValue sẽ được sử dụng để tính toán người chiến thắng:

.square {
  background: 
# fff;
  border: 1px solid 
# 999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}
.styleX {
  color: 
# 345594;
  font-weight: 700;
}
.styleO {
  color: red;
  font-weight: 700;
}
.square:focus {
  outline: none;
}

5

Vậy hàm calculateWinnerNew có thể trả ra 2 giá trị: Nếu Chain đủ 5 ô cùng giá trị, trả ra người chơi hiện tại, nếu không, kết quả trả ra null.

Quay trở lại với componet Board và hoàn thành nốt phần TODO trong hàm handleClick:

.square {
  background: 
# fff;
  border: 1px solid 
# 999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}
.styleX {
  color: 
# 345594;
  font-weight: 700;
}
.styleO {
  color: red;
  font-weight: 700;
}
.square:focus {
  outline: none;
}

6

Và lúc này, khi tìm được người chiến thắng, phần status cũng sẽ phải hiển thị thông tin người thắng cuộc, cùng chỉnh sửa một chút nha:

.square {
  background: 
# fff;
  border: 1px solid 
# 999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}
.styleX {
  color: 
# 345594;
  font-weight: 700;
}
.styleO {
  color: red;
  font-weight: 700;
}
.square:focus {
  outline: none;
}

7

Giờ đây, giao diện sẽ thay đổi thành:

Restart game

Ngoài lề một chút, nếu như ta muốn restart game mà không cần F5 trang, có thể tạo 1 chiếc button và return tất cả về trạng thái mặc định.

Chủ Đề