亲宝软件园·资讯

展开

HTML+CSS+JavaScript井字游戏

海拥✘ 人气:0

演示地址

实现 HTML

首先在 head 部分,我将包含我们稍后创建的 css 和 javascript 文件。我还添加了名为 Itim 的 Google 字体。

<link rel="stylesheet" href="style.css" rel="external nofollow" >
<link rel="preconnect" href="https://fonts.gstatic.com" rel="external nofollow" >
<link href="https://fonts.googleapis.com/css2?family=Itim&display=swap" rel="external nofollow"  rel="stylesheet">
<script src="index.js"></script>

HTML 的主体将相当简单。为了包装所有东西,我将使用一个主标签,并对其应用一个类background。在main包装器内部,我们将有五个部分。

第一部分将只包含我们的标题h1。

第二部分将显示当前轮到谁。在显示中,我们有一个包含X或O取决于当前用户的跨度。我们将类应用于此跨度以对文本进行着色。

第三部分是拿着游戏板的部分。它有一个container类,因此我们可以正确放置瓷砖。在本节中,我们有 9 个 div,它们将充当板内的瓷砖。

第四部分将负责公布最终比赛结果。默认情况下它是空的,我们将从 javascript 修改它的内容。

最后一部分将保存我们的控件,其中包含一个重新开始按钮。

<main class="background">
        <section class="title">
            <h1>井字游戏</h1>
        </section>
        <section class="display">
            玩家 <span class="display-player playerX">X</span> 的回合
        </section>
        <section class="container">
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
        </section>
        <section class="display announcer hide"></section>
        <section class="controls">
            <button id="reset">重新开始</button>
        </section>
    </main>

添加 CSS

我不会详细介绍 CSS 的每一行,但你可以查看源码中的完整代码。

首先,我将创建style.css文件并删除任何浏览器定义的边距和填充,并为整个文档设置我在 HTML 中包含的 Google 字体。

* {
    padding: 0;
    margin: 0;
    font-family: 'Itim', cursive;
}

我们必须添加的下一件重要事情是我们的板的样式。我们将使用 CSS 网格来创建板。我们可以通过为列和行提供 3 倍 33% 的空间将容器一分为二。我们将通过设置最大宽度和将容器居中margin: 0 auto;

.container {
    margin: 0 auto;
    display: grid;
    grid-template-columns: 33% 33% 33%;
    grid-template-rows: 33% 33% 33%;
    max-width: 300px;
}

接下来,我们将添加板内瓷砖的样式。我们将应用一个小的白色边框,并将最小宽度和高度设置为 100 像素。我们将利用Flexbox的和设置的中心内容justify-content align-items到center。我们会给它一个大字体大小并应用,cursor: pointer这样用户就会知道这个字段是可点击的。

.tile {
    border: 1px solid white;
    min-width: 100px;
    min-height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 50px;
    cursor: pointer;
}

我使用了两种不同的颜色来更好地区分这两个玩家。为此,我创建两个实用程序类。玩家 X 的颜色为绿色,而玩家 O 的颜色为蓝色。

.playerX {
    color: #09C372;
}

.playerO {
    color: #498AFB;
}

实现 Javascript 部分

由于我们将 javascript 文件包含在<head>. 这是必需的,因为我们的脚本将在浏览器解析 HTML 正文之前加载。如果你不想将所有内容都包含在此函数中,请随意添加defer到脚本标记中或将脚本标记移动到body.

window.addEventListener('DOMContentLoaded', () => {

});

首先,我们将保存对 DOM 节点的引用。我们将使用document.querySelectorAll(). 我们想要一个数组,但此函数返回一个 NodeList,因此我们必须使用Array.from(). 我们还将获取对播放器显示、重置按钮和播音员的引用。

const tiles = Array.from(document.querySelectorAll('.tile'));
const playerDisplay = document.querySelector('.display-player');
const resetButton = document.querySelector('#reset');
const announcer = document.querySelector('.announcer');

接下来,我们将添加控制游戏所需的全局变量。我们将用一个包含九个空字符串的数组来初始化一个板。这将保存板上每个图块的 X abd O 值。我们将有一个currentPlayer持有当前回合活跃的玩家的标志。该isGameActive变量将一直为真,直到有人获胜或游戏以平局结束。在这些情况下,我们会将其设置为 false,以便剩余的图块在重置之前处于非活动状态。我们有三个常数代表游戏结束状态。我们使用这些常量来避免拼写错误。

let board = ['', '', '', '', '', '', '', '', ''];
let currentPlayer = 'X';
let isGameActive = true;

const PLAYERX_WON = 'PLAYERX_WON';
const PLAYERO_WON = 'PLAYERO_WON';
const TIE = 'TIE';

在下一步中,我们将在棋盘上存储所有获胜的位置。在每个子数组中,我们将存储可以赢得比赛的三个位置的索引。所以这[0, 1, 2]将代表第一条水平线被玩家占据的情况。我们将使用这个数组来决定我们是否有赢家。

/*
   Indexes within the board
   [0] [1] [2]
   [3] [4] [5]
   [6] [7] [8]
*/

const winningConditions = [
   [0, 1, 2],
   [3, 4, 5],
   [6, 7, 8],
   [0, 3, 6],
   [1, 4, 7],
   [2, 5, 8],
   [0, 4, 8],
   [2, 4, 6]
];

现在我们将编写一些实用函数。在isValidAction函数中,我们将决定用户是否想要执行有效的操作。如果 tile 的内部文本是XorO我们返回 false 作为操作无效,否则 tile 为空所以操作有效。

const isValidAction = (tile) => {
    if (tile.innerText === 'X' || tile.innerText === 'O'){
        return false;
    }

    return true;
};

下一个效用函数将非常简单。在这个函数中,我们将接收一个索引作为参数,并将棋盘数组中的相应元素设置为我们当前玩家的符号。

const updateBoard =  (index) => {
   board[index] = currentPlayer;
}

我们将编写一个小函数来处理玩家的变化。在这个函数中,我们将首先从playerDisplay. 字符串模板文字player${currentPlayer}将成为playerX或playerO取决于当前玩家。接下来,我们将使用三元表达式来更改当前玩家的值。如果是X,它将是O否则它将是X。现在,我们改变了我们用户的价值,我们需要更新innerText的playerDisplay,并应用新的播放器类的。

const changePlayer = () => {
    playerDisplay.classList.remove(`player${currentPlayer}`);
    currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
    playerDisplay.innerText = currentPlayer;
    playerDisplay.classList.add(`player${currentPlayer}`);
}

现在我们将编写宣布最终游戏结果的 announer 函数。它将接收结束游戏类型并innerText根据结果更新播音员 DOM 节点的 。在最后一行中,我们必须删除隐藏类,因为播音员默认是隐藏的,直到游戏结束。

const announce = (type) => {
    switch(type){
       case PLAYERO_WON:
            announcer.innerHTML = 'Player <span class="playerO">O</span> Won';
            break;
       case PLAYERX_WON:
            announcer.innerHTML = 'Player <span class="playerX">X</span> Won';
            break;
       case TIE:
            announcer.innerText = 'Tie';
        }
    announcer.classList.remove('hide');
};

接下来我们将编写这个项目中最有趣的部分之一——结果评估。首先,我们将创建一个 roundWon 变量并将其初始化为 false。然后我们将遍历winConditions数组并检查棋盘上的每个获胜条件。例如,在第二次迭代中,我们将检查这些值:board3、board4、board5。

我们还将进行一些优化,如果任何字段为空,我们将调用continue并跳到下一次迭代,因为如果获胜条件中有空图块,您将无法获胜。如果所有字段都相等,那么我们就有一个赢家,因此我们将 roundWon 设置为 true 并中断 for 循环,因为任何进一步的迭代都会浪费计算。

在循环之后,我们将检查roundWon变量的值,如果为真,我们将宣布获胜者并将游戏设置为非活动状态。如果我们没有获胜者,我们将检查棋盘上是否有空牌,如果我们没有获胜者并且没有空牌,我们将宣布平局。

function handleResultValidation() {
  let roundWon = false;
  for (let i = 0; i <= 7; i++) {
    const winCondition = winningConditions[i];
    const a = board[winCondition[0]];
    const b = board[winCondition[1]];
    const c = board[winCondition[2]];
    if (a === "" || b === "" || c === "") {
      continue;
    }
    if (a === b && b === c) {
      roundWon = true;
      break;
    }
  }

  if (roundWon) {
    announce(currentPlayer === "X" ? PLAYERX_WON : PLAYERO_WON);
    isGameActive = false;
    return;
  }

  if (!board.includes("")) announce(TIE);
}

接下来我们将处理用户的操作。此函数将接收一个 tile 和一个索引作为参数。当用户单击一个图块时,将调用此函数。首先我们需要检查它是否是一个有效的动作,我们还将检查游戏当前是否处于活动状态。如果两者都为真,我们innerText用当前玩家的符号更新瓷砖的 ,添加相应的类并更新板阵列。现在一切都更新了,我们必须检查游戏是否已经结束,所以我们调用handleResultValidation(). 最后,我们必须调用该changePlayer方法将轮次传递给另一个玩家。

const userAction = (tile, index) => {
  if (isValidAction(tile) && isGameActive) {
    tile.innerText = currentPlayer;
    tile.classList.add(`player${currentPlayer}`);
    updateBoard(index);
    handleResultValidation();
    changePlayer();
  }
};

为了让游戏正常运行,我们必须向磁贴添加事件侦听器。我们可以通过循环遍历图块数组并为每个图块添加一个事件侦听器来做到这一点。(为了获得更好的性能,我们只能向容器添加一个事件侦听器并使用事件冒泡来捕获父级上的磁贴点击,但我认为对于初学者来说这更容易理解。)

tiles.forEach( (tile, index) => {
    tile.addEventListener('click', () => userAction(tile, index));
});

我们只错过了一项功能:重置游戏。为此,我们将编写一个resetBoard函数。在此函数中,我们将棋盘设置X为由九个空字符串组成,将游戏设置为活动状态,移除播音员并将玩家更改回(根据定义X始终开始)。

我们必须做的最后一件事是遍历图块并将innerText 设置回空字符串,并从图块中删除任何特定于玩家的类。

const resetBoard = () => {
    board = ['', '', '', '', '', '', '', '', ''];
    isGameActive = true;
    announcer.classList.add('hide');

    if (currentPlayer === 'O') {
        changePlayer();
    }

    tiles.forEach(tile => {
        tile.innerText = '';
        tile.classList.remove('playerX');
        tile.classList.remove('playerO');
    });
}

现在我们只需要将此函数注册为重置按钮的点击事件处理程序。

resetButton.addEventListener('click', resetBoard);

就是这样,我们有一个功能齐全的井字游戏,你可以和你的朋友一起玩,玩得开心。

加载全部内容

相关教程
猜你喜欢
用户评论