65.9K
CodeProject 正在变化。 阅读更多。
Home

创建基于浏览器的文字游戏

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.44/5 (2投票s)

2024年8月15日

CPOL

15分钟阅读

viewsIcon

2776

downloadIcon

56

本文介绍了我编写一个模仿 Wordle 的单词游戏的方法,以及使用相同技术编写的其他游戏。

引言

本文介绍了依赖于字典和单词查找的单词游戏的开发。它以 Wordle 的克隆为主要目标,但也涵盖了使用相同技术构建的其他游戏。它将展示如何存储字典,如何快速查找单词,如何使用 HTML 定义棋盘、磁贴和屏幕键盘,如何使用 CSS 和 W3 为不同屏幕分辨率设置样式,如何使用 JavaScript 事件,以及如何使用 jQuery,特别是其对动画的支持。我的 Wordle 克隆在底层与 Wordle 完全不同。它使用不同的字典,外观和感觉不同,有更多的选项,例如四字母和六字母的变体,并且更有趣。我称我的版本为 GuessWord。我还将讨论 Griddle,一个比 Wordle 更具挑战性的拼字游戏,以及 Word Pyramid,一个基于查找字谜的单词游戏。

构建 HTML 页面结构

Guessword 自包含在一个 HTML 页面中。一旦页面被加载到浏览器窗口中,它就不会与服务器或其他应用程序通信。初始屏幕有一个标题栏,其中包含一个使用 W3 样式构建的简单菜单。

Play 启动一个新游戏。

About 隐藏页面主体,并显示一个包含游戏信息和游戏说明的面板。

GuessWord Why & How

History 显示一个页面,其中包含已玩游戏和获胜所用回合数的统计信息。

Player's record'

Games 让您选择 4 字母、5 字母和 6 字母的游戏。

Games Menu

创建游戏棋盘和屏幕键盘

最好使用 HTML 定义元素,但将所有样式设计留给 CSS,将操作留给 JavaScript 和 jQuery。为此,每个 HTML 元素都有唯一的 ID 并被分配了一个类。这是 GuessWord 中字母输入网格的 HTML 布局。

        <div id="boardleftmargin" class="fivecolmargin">
            <div id="column row0">
                <span id="0_0" class="gridletter empty delay0 w3-cell"></span>
                <span id="0_1" class="gridletter empty delay1 w3-cell"></span>
                <span id="0_2" class="gridletter empty delay2 w3-cell"></span>
                <span id="0_3" class="gridletter empty delay3 w3-cell"></span>
                <span id="0_4" class="gridletter empty delay4 w3-cell"></span>
                <span id="0_5" class="gridletter empty delay5 w3-cell"></span>
            </div>
            <div id="column row1">
                <span id="1_0" class="gridletter empty delay0 w3-cell"></span>
                <span id="1_1" class="gridletter empty delay1 w3-cell"></span>
                <span id="1_2" class="gridletter empty delay2 w3-cell"></span>
                <span id="1_3" class="gridletter empty delay3 w3-cell"></span>
                <span id="1_4" class="gridletter empty delay4 w3-cell"></span>
                <span id="1_5" class="gridletter empty delay5 w3-cell"></span>
            </div>
            <div id="column row2">
                <span id="2_0" class="gridletter empty delay0 w3-cell"></span>
                <span id="2_1" class="gridletter empty delay1 w3-cell"></span>
                <span id="2_2" class="gridletter empty delay2 w3-cell"></span>
                <span id="2_3" class="gridletter empty delay3 w3-cell"></span>
                <span id="2_4" class="gridletter empty delay4 w3-cell"></span>
                <span id="2_5" class="gridletter empty delay5 w3-cell"></span>
            </div>
            <div id="column row3">
                <span id="3_0" class="gridletter empty delay0 w3-cell"></span>
                <span id="3_1" class="gridletter empty delay1 w3-cell"></span>
                <span id="3_2" class="gridletter empty delay2 w3-cell"></span>
                <span id="3_3" class="gridletter empty delay3 w3-cell"></span>
                <span id="3_4" class="gridletter empty delay4 w3-cell"></span>
                <span id="3_5" class="gridletter empty delay5 w3-cell"></span>
            </div>
            <div id="column row4">
                <span id="4_0" class="gridletter empty delay0 w3-cell"></span>
                <span id="4_1" class="gridletter empty delay1 w3-cell"></span>
                <span id="4_2" class="gridletter empty delay2 w3-cell"></span>
                <span id="4_3" class="gridletter empty delay3 w3-cell"></span>
                <span id="4_4" class="gridletter empty delay4 w3-cell"></span>
                <span id="4_5" class="gridletter empty delay5 w3-cell"></span>
            </div>
            <div id="column row5">
                <span id="5_0" class="gridletter empty delay0 w3-cell"></span>
                <span id="5_1" class="gridletter empty delay1 w3-cell"></span>
                <span id="5_2" class="gridletter empty delay2 w3-cell"></span>
                <span id="5_3" class="gridletter empty delay3 w3-cell"></span>
                <span id="5_4" class="gridletter empty delay4 w3-cell"></span>
                <span id="5_5" class="gridletter empty delay5 w3-cell"></span>
            </div>
        </div>

        <div id="6_0" class="bottomspacer">

        </div>

如果我们想引用第二行第三个字母,使用 jQuery,我们可以简单地写 $("#1_2")。实际上,我们会使用方法和变量来生成引用。每个单元格都有三个 CSS 类。w3-cell 利用 W3 样式表提供的样式。gridletter 告诉浏览器如何渲染字母,而 empty 表示单元格尚未填充。类可以在运行时使用 JavaScript 和 jQuery 进行操作。

屏幕键盘遵循相同的原则。

        <div id="keyboard">
            <div class="firstkeyfirstrow">
                <div id="q" class="key delay5 empty w3-cell">Q</div>
                <div id="w" class="key delay5 empty w3-cell">W</div>
                <div id="e" class="key delay5 empty w3-cell">E</div>
                <div id="r" class="key delay5 empty w3-cell">R</div>
                <div id="t" class="key delay5 empty w3-cell">T</div>
                <div id="y" class="key delay5 empty w3-cell">Y</div>
                <div id="u" class="key delay5 empty w3-cell">U</div>
                <div id="i" class="key delay5 empty w3-cell">I</div>
                <div id="o" class="key delay5 empty w3-cell">O</div>
                <div id="p" class="key delay5 empty w3-cell">P</div>
            </div>
            <div class="firstkeysecondrow">
                <div id="a" class="key delay5 empty w3-cell">A</div>
                <div id="s" class="key delay5 empty w3-cell">S</div>
                <div id="d" class="key delay5 empty w3-cell">D</div>
                <div id="f" class="key delay5 empty w3-cell">F</div>
                <div id="g" class="key delay5 empty w3-cell">G</div>
                <div id="h" class="key delay5 empty w3-cell">H</div>
                <div id="j" class="key delay5 empty w3-cell">J</div>
                <div id="k" class="key delay5 empty w3-cell">K</div>
                <div id="l" class="key delay5 empty w3-cell">L</div>
            </div>
            <div class="firstkeythirdrow">
                <div id="z" class="key delay5 empty w3-cell">Z</div>
                <div id="x" class="key delay5 empty w3-cell">X</div>
                <div id="c" class="key delay5 empty w3-cell">C</div>
                <div id="v" class="key delay5 empty w3-cell">V</div>
                <div id="b" class="key delay5 empty w3-cell">B</div>
                <div id="n" class="key delay5 empty w3-cell">N</div>
                <div id="m" class="key delay5 empty w3-cell">M</div>
            </div>
            <div id="buttonrow" class="firstkeyfirstrow">
                <div id="backspace" class="backspacekey delay5 empty w3-cell">
                    ⌫ Backspace
                </div>
                <div id="enter" class="enterkey delay5 empty w3-cell">
                    Enter ⏎
                </div>
            </div>
        </div>

运行时,它们看起来是这样的

Layout And Keyboard

布局和屏幕键盘

设置游戏棋盘和屏幕键盘的样式

这些是根据字母和键盘状态控制其外观的 CSS 样式。例如,位于正确位置的字母会将 empty 类替换为 green 类。

            .gridletter {
                border: 1px solid black;
                box-shadow: 3px 3px 5px #999;
                cursor: pointer;
                text-align: center;
            }

            .key, .backspacekey, .enterkey {
                border: 1px solid;
                text-shadow: 1px 0 0 black;
                cursor: pointer;
                text-align: center;
                vertical-align: middle;
            }

            .highlight {
                border: 3px dashed red;
                padding: 0;
            }

            @keyframes rotate {
                0% {
                    transform: rotateY(90deg);
                }
            }

            .empty {
                background: linear-gradient(135deg, rgb(245,245,245), rgb(250,235,215));
            }

            .green {
                background: linear-gradient(135deg, chartreuse, seagreen);
                animation-duration: 1.5s;
                animation-name: rotate;
                animation-fill-mode: forwards;
            }

            .grey {
                background: linear-gradient(135deg, rgb(220,220,220), rgb(119,136,153));
                animation-duration: 1.5s;
                animation-name: rotate;
                animation-fill-mode: forwards;
            }

            .yellow {
                background: linear-gradient(135deg, lightyellow, gold);
                animation-duration: 1.5s;
                animation-name: rotate;
                animation-fill-mode: forwards;
            }

            .red {
                background: linear-gradient(135deg, rgb(255,192,203),rgb(240,128,128));
                animation-duration: 1.5s;
                animation-name: rotate;
                animation-fill-mode: forwards;
            }

            .greengrad {
                background: linear-gradient(0deg, mediumseagreen, green);
                border: 1px solid;
                border-radius: 8px;
                box-shadow: 3px 3px 5px #999;
            }

请注意,CSS 提供了微妙的背景渐变和 CSS 动画属性。当类更改时,动画会旋转字母磁贴。已完成的游戏看起来是这样的。

Completed Game

字母和按键的 CSS

用 JavaScript 编写游戏逻辑

GuessWord 在文档“准备好”时初始化游戏。它使用 jQuery 来确定就绪状态。

    $(document).ready(function () {
    //
    // Load stylesheet based on screen size
    LoadSizingStyleSheet();

    const AttachEvents = function () {
        //
        // Add Events to Keyboard letter
        $(".key").on('click', function (e) {
            if ($(".key").prop('disabled')) {
                return;
            }
            let letter = e.target.id.toUpperCase();
            SetLetter(letter);
        });

        ...

        $(window).resize(function () {
            LoadSizingStyleSheet();
        });
    }
    document.addEventListener('keyup', function (e) {
        let key = e.key;
        let letter = "";
        
        ...

        switch (key) {
            case "Shift":
            case "Control":
            case "Alt":
                break;
            case "Backspace":
                ProcessBackspace();
                break;
            case "Enter":
                ProcessEnter();
                break;
            default:
                if ($(".key").prop('disabled')) {
                    return;
                }
                if (g.EndOfGame) {
                    return;
                }
                if (key >= "a" && key <= "z") {
                    letter = key.toUpperCase();
                }
                else if (key >= "A" && key <= "Z") {
                    letter = key;
                }
                if (letter != "") {
                    SetLetter(letter);
                }
                break;
                }
            });
            AttachEvents();
            SetColunmVisibility(5, false);
            LocalStorageAvailable();
            SetEmailField();
            SetCopyRight();
            StartGame();
        });

GuessWord 使用一个对象来存储全局信息和游戏状态。这是声明和实例化。

        const Global = function () {
            this.CurrentPos = 0;
            this.LastPos = -1;
            this.GridWidth = 5;
            this.CurrentRow = 0;
            this.ActiveCell = '';
            this.Positions = [
                "0_0", "0_1", "0_2", "0_3", "0_4", "0_5",
                "1_0", "1_1", "1_2", "1_3", "1_4", "1_5",
                "2_0", "2_1", "2_2", "2_3", "2_4", "2_5",
                "3_0", "3_1", "3_2", "3_3", "3_4", "3_5",
                "4_0", "4_1", "4_2", "4_3", "4_4", "4_5",
                "5_0", "5_1", "5_2", "5_3", "5_4", "5_5"
            ];
            this.Words = ['', '', '', '', '', ''];
            this.Mode = normalMode;
            this.LocalStorageAvailable = true;
            this.Alphabet = "abcdefghijklmnopqrstuvwxyz";
            this.Letter = " "
            this.CurrentPage = page_play;
            this.EndOfRow = false;
            this.EndOfGame = false;
            this.Won = false;
            this.TargetWord = "PRICE";
            this.KeyBoardState = kb_StartOfWord;
            this.ColorCodeQueue = [];
        }
        var g = new Global;

您可以以多种不同的方式声明函数,我选择了这种风格。

               const {function name} = function ({arguments}) {
                  {body of function}
               }

例如,此函数返回字母磁贴的 ID,给出行号和列号,以便在 jQuery 引用磁贴时使用。

            const GetID = function (row, col) {
                return "#" + row.toString() + "_" + col.toString();
            }

我使用相同的风格来声明对象。

我的 JavaScript 代码中充斥着关于 Substr 已被弃用的严厉警告。他们怎么敢弃用一个具有如此悠久历史的函数!自从 PL/1 成为取代 Fortran 和 Cobol 的下一个重要事物以来,我一直在使用 Substr。它一直存在于 Visual Basic 和 C# 中,但现在要被废除了吗?所以,我问我的好朋友 ChatGTP 创建一个“mysubstr”方法来替换用作方法的“substr”。这是它创建的。

            String.prototype.mysubstr = function (start, length) {
                // If start is negative, calculate start from the end of the string
                if (start < 0) {
                    start = this.length + start;
                }

                // Ensure start is not less than 0
                start = Math.max(start, 0);

                // Calculate the end index based on start and length
                let end = start + length;

                // Use slice to get the substring
                return this.slice(start, end);
            };

使用 jQuery 简化代码

jQuery 可以轻松编写引用或修改 HTML 元素的 कोड。当然,它能做的远不止这些,但对于像 GuessWord 这样的简单应用程序,仅使用该 jQuery 功能就可以让编码轻松得多。例如,响应屏幕键盘上的按键点击只需要很少的代码。

            $(".key").on('click', function (e) {
                if ($(".key").prop('disabled')) {
                    return;
                }
                let letter = e.target.id.toUpperCase();
                SetLetter(letter);
            });

如果我们使用纯 JavaScript 并且我们是好的编码员,我们可能会用这样的代码来完成同样的事情

            const keys = document.querySelectorAll('.key');
            keys.forEach(function(key) {
                key.addEventListener('click', function(e) {
                    if (Array.from(keys).some(key => key.disabled)) {
                        return;
                    }
                    let letter = e.target.id.toUpperCase();
                    SetLetter(letter);
                });
            });

新手可能会写这样的代码

            // Get all elements with the class 'key'
            var allElements = document.getElementsByTagName('*'); // Get all elements in the document
            var keys = [];
            for (var i = 0; i < allElements.length; i++) {
                if (allElements[i].classList.contains('key')) {
                    keys.push(allElements[i]); // Add elements with class 'key' to the keys array
                }
            }

            // Add click event listener to each key
            for (var i = 0; i < keys.length; i++) {
                keys[i].addEventListener('click', function(e) {
                    // Check if any key is disabled
                    var isDisabled = false;
                    for (var j = 0; j < keys.length; j++) {
                        if (keys[j].disabled) {
                            isDisabled = true;
                            break;
                        }
                    }
                    if (isDisabled) {
                        return;
                    }

                    // Get the letter from the clicked key's id and call SetLetter
                    var letter = e.target.id.toUpperCase();
                    SetLetter(letter);
                });
            }

玩游戏

输入字母

玩家使用物理键盘或屏幕键盘输入单词。每输入一个字母,就会选择下一个可用的磁贴并填充字母。由于我们不是在 HTML 文本输入区域中键入,因此我们需要一种方法将磁贴与键盘连接起来。我们使用 jQuery 选择器为此屏幕键盘上的每个按键添加事件,代码如下。

        //
        // Add Events to Keyboard letter
        $(".key").on('click', function (e) {
            if ($(".key").prop('disabled')) {
                return;
            }
            let letter = e.target.id.toUpperCase();
            SetLetter(letter);
        });

如果用户使用键盘,我们会使用事件监听器来处理每次击键。这是键盘监听器的代码。

          document.addEventListener('keyup', function (e) {
            let key = e.which;
            let letter = "";
            let shift = e.shiftKey;
            let ctrl = e.ctrlKey;
            // Stop invoking debugger passing on "I".
            if (shift && ctrl) {
                return;
            }
            // Don't let copy/paste through
            if (ctrl && (key == 67 || key == 86)) {
                return;
            }
            switch (key) {
                case 8:
                    ProcessBackspace();
                    break;
                case 13:
                    ProcessEnter();
                    break;
                default:
                    if ($(".key").prop('disabled')) {
                        return;
                    }
                    if (g.EndOfGame) {
                        return;
                    }
                    if (key >= 65 && key <= 90) {
                        letter = g.Alphabet.mysubstr(key - 65, 1).toUpperCase();
                    }
                    else if (key >= 97 && key <= 122) {
                        letter = g.Alphabet.mysubstr(key - 97, 1);
                    }
                    if (letter != "") {
                        SetLetter(letter);
                    }
                    break;
            }
        });

无论字母如何输入,都会使用 SetLetter 函数来处理字母。SetLetter 中的关键语句是

       $("#" + g.Positions[g.CurrentPos]).text(letter);

其中 g.Postions[g.CurrentPos] 返回一个键标识符,例如“4_2”。

处理单词

Guessword 的帮助选项卡解释了规则。

您的任务是推断 GuessWord 从其常用单词字典中随机选择的猜测单词。您可以使用屏幕键盘或物理键盘(如果可用)输入您的猜测。

当您选择“enter”时,GuessWord 会根据以下规则为您的猜测中的每个字母着色

如果字母不在猜测单词中,则将其着色为灰色。

如果字母存在于猜测单词中但位置不正确,则将其着色为黄色。

如果字母存在且位置正确,则将其着色为绿色。

如果猜测不在 GuessWord 的几乎所有单词字典中,则猜测中的所有字母都将显示为红色。

如果您的猜测包含一个在目标单词中出现不止一次的字母,那么只有正确位置上的字母才会显示为绿色。否则,目标单词中的第一个匹配项将显示为黄色。

如果您的猜测包含相同的字母不止一次,那么只有正确位置上的字母才会显示为绿色。否则,猜测中的第一个匹配项将显示为黄色。

您可以使用退格键 (⌫) 清除猜测。或者,您可以单击或点击单词中的字母,然后使用任一键盘进行更改。执行此操作时,您点击的字母会以红色虚线边框突出显示。输入字母或仅单击或点击高亮显示的单元格后,边框将被清除。如果单词已完成并且您对其进行了更正,Guessword 将删除红色,但前提是该单词在其完整的字典中。

实现这些规则的逻辑有点棘手。对我来说,关键是将查找绿色字母与黄色字母分开。这是处理猜测的函数。

        const WordAccepted = function (row) {
            g.ColorCodeQueue = [];
            let letter = '';
            let letterID = '';
            let originalguess = '';
            let guess = GetGuess(row);
            originalguess = guess;
            let found = ValidWord(guess.toLowerCase());
            if (!found) {
                for (let i = 0; i < g.GridWidth; i++) {
                    let letterID = GetID(row, i);
                    ChangeColorClass(letterID, red);
                }
                KeyBoardState(kb_BadWord);
                return false;
            }
            else {
                //
                // Set all letters in grid to grey (missing)
                for (let i = 0; i < g.GridWidth; i++) {
                    let letterID = GetID(row, i);
                    let letter = guess.mysubstr(i, 1);
                    let keyID = "#" + letter.toLowerCase();
                    SetKeyColorCode(greyCode, keyID);
                    // Force grey elements to front of pairs with same letterID
                    QueueColorCode("aaaa", letterID);
                }
                let targetWord = g.TargetWord;
                //
                // Loop through the target word looking for exact matches
                for (let i = 0; i < g.GridWidth; i++) {
                    let letter = guess.mysubstr(i, 1);
                    if (letter == targetWord.mysubstr(i, 1)) {
                        letterID = GetID(row, i);
                        let keyID = "#" + letter.toLowerCase();
                        SetKeyColorCode(greenCode, keyID);
                        QueueColorCode(green, letterID);
                        targetWord = HideMatch(targetWord, i, '?');
                        guess = HideMatch(guess, i, '!');
                    }
                }
                //
                // Loop through the target word looking for inexact matches
                for (let i = 0; i < g.GridWidth; i++) {
                    let letter = guess.mysubstr(i, 1);
                    let pos = targetWord.indexOf(letter);
                    if (pos >= 0) {
                        letterID = GetID(row, i);
                        let keyID = "#" + letter.toLowerCase();
                        SetKeyColorCode(yellowCode, keyID);
                        QueueColorCode(yellow, letterID);
                        targetWord = HideMatch(targetWord, pos, '?');
                        guess = HideMatch(guess, i, '!');
                    }
                }
                ProcessColorCodeQueue();
                g.TargetWord = g.TargetWord.toUpperCase();
                originalguess = originalguess.toUpperCase();
                g.Words[g.CurrentRow] = originalguess;
                g.Won = originalguess == g.TargetWord
                if (g.Won || g.CurrentRow == 5) {
                    g.EndOfGame = true;
                    KeyBoardState(kb_EndOfGame);
                    letterID = GetID(g.CurrentRow + 1, 0);
                    // Display finish message after tiles colored - note CurrentRow is incremented before timeout executes
                    setTimeout(function () { ShowMessage(GetMessage(g.CurrentRow + (g.Won ? -1 : 0)), letterID, DoNothing) }, 2000);
                }
                g.CurrentRow++;
                g.EndOfRow = false;
                KeyBoardState(kb_StartOfWord);
                return true;
            }
}

字典

首先映入脑海的问题是,如何在不使用后端服务器的情况下为浏览器应用程序提供完整的英语字典?答案是,包含所有 5 字母单词的文件大约为 50KB。浏览器可以处理 20MB 或更大的图像,因此 50KB 算不了什么。Guessword 只将所有单词放入一个 JavaScript 数组。从调试模式下窥视 Wordle 来看,我认为它也做了同样的事情。所以,我们的字典看起来是这样的

            const _FullDict5 = [
                "aahed",
                "aalii",
                "aargh",
                ...
                "zowie",
                "zuzim",
                "zymes"
            ];

Guessword 内置了 4 字母和 6 字母的变体,因此它也有所有 4 字母和 6 字母单词的字典。如果我们允许任何单词作为猜测单词,那么很多人会问“aalii 是什么?”所以,我们需要常用单词的字典,这些字典可以用作猜测单词。我使用了互联网上的各种来源,拼凑了常用 4、5 和 6 字母单词的字典。下表显示了每个字典中的单词数。

Dictionary Sizes

字典大小(字节)

将所有字符加起来,我们得到 195,950 字节。

我们还需要一种方法来为每个游戏选择猜测单词。我使用 Excel 为从 1 到每个目标字典中单词数的列表生成数字。然后我使用 =Rand() 为每个序列列中的每个数字创建随机数。我使用随机数作为键对三个列对进行排序。这给了我三个随机序列的数字列表,我将它们复制到 Guess Word 字典文件中。当用户第一次玩 Guess Word 时,它会生成一个 0 到字典中单词数的范围内的随机数。这个数字是起始索引,对于每个玩家来说应该接近唯一。当新游戏开始时,Guess Word 会检索起始索引,并使用它从目标字典中选择相应的单词。然后它会增加起始索引。 casual debugger 无法通过查看 Guess Word 字典来确定当前的猜测单词。

Guess Word 使用二分查找来检查一个单词是否在字典中。它使用的函数是

            const ValidWord = function (word) {
                let valid = true;
                switch (g.GridWidth) {
                    case 4:
                        if (BinSearch(_FullDict4, word) < 0) {
                            valid = false;
                        }
                        break;
                    case 5:
                        if (BinSearch(_FullDict5, word) < 0) {
                            valid = false;
                        }
                        break;
                    case 6:
                        if (BinSearch(_FullDict6, word) < 0) {
                            valid = false;
                        }
                        break;
                    default:
                        alert('oops');
                }
                return valid;
            }
            const BinSearch = function (dictonary, word) {
                if (!dictonary.length) return -1;

                let high = dictonary.length - 1;
                let low = 0;
                let mid = 0;
                while (low <= high) {
                    mid = parseInt((low + high) / 2);
                    const element = dictonary[mid];
                    if (element > word) {
                        high = mid - 1;
                    }
                    else if (element < word) {
                        low = mid + 1;
                    }
                    else {
                        return mid;
                    }
                }
                return -1;
            };

动画

计算机游戏需要一点动画效果,即使它们只是像纸牌和拼字游戏这样的实体游戏的实现。Guess Word 使用 CSS 动画通过旋转磁贴来更改颜色,以显示新颜色。当字母的 CSS 类从 empty 更改为 green(例如)时,动画就会启动。CSS 看起来是这样的

            @keyframes rotate {
            0% {
                transform: rotateY(90deg);
                }
            }

            .empty {
                background: linear-gradient(135deg, rgb(245,245,245), rgb(250,235,215));
            }

            .green {
                background: linear-gradient(135deg, chartreuse, seagreen);
                animation-duration: 1.5s;
                animation-name: rotate;
                animation-fill-mode: forwards;
            }
            etc.

如果通过在简单循环中切换字母到计算出的类来为完成的单词设置动画,那么所有字母将同时旋转。这看起来有点傻。因此,我们根据列延迟动画的开始,并为每列应用额外的样式。我们还将 delay5 样式应用于键盘字母,以便它们在字母输入网格中的字母重新着色后重新着色。

            .delay0 {
                animation-delay: 0ms;
            }
            .delay1 {
                animation-delay: 400ms;
            }
            .delay2 {
                animation-delay: 800ms;
            }
            .delay3 {
                animation-delay: 1200ms;
            }
            .delay4 {
                animation-delay: 1600ms;
            }
            .delay5 {
                animation-delay: 2000ms;
            }

奖励胜利(或不奖励)

当玩家获胜或失败时,Guess Word 会显示一个模态消息。请注意,消息是在磁贴旋转后显示的。这会带来一点紧张感,因为玩家等待看所有字母是否都变成绿色。通过将一个半透明的 div 覆盖在棋盘上,位于消息框下方,可以将消息设为模态。消息是高尔夫主题的。这是狭义获胜的一个例子

Double Bogey

花费 6 回合而不是 4 回合(标准杆)

失败更糟。

Lost

失败

Guess Word 的四字母和六字母版本

您可以在四字母、五字母和六字母版本之间进行选择。添加更多变体很容易,一些程序员发布了多达 12 个字母的 Wordle 克隆。尝试之后,四字母是您能进行的最低限制,因为它变得非常容易获胜,而超过六个字母则变得相当困难,因为很难想出符合已识别和定位字母约束的七个或更多字母的单词。

Daily challenge 选择一个由日期标识的 5 字母猜测单词。这模仿了 Wordle,它在同一天给所有玩家相同的猜测单词。

使用多个 CSS 文件进行尺寸调整

我使用一种有些非正统的方法来允许游戏根据托管它的设备进行调整,无论是智能手机、移动设备、平板电脑、笔记本电脑还是台式机。我有一个控制外观的样式表,然后为从 312px 到 1028px 的每个可能屏幕宽度使用一个样式表来控制尺寸。这似乎是个糟糕的主意,有大约 300 个样式表,每个宽度 +/- 2 像素一个。诀窍是有一个程序可以轻松生成这些样式表。在运行时,Guess Word 只下载与当前屏幕宽度匹配的样式表;而不是所有 300 个版本。

我们从两个名为 Guess WordFull 和 Guess WordBase 的样式表开始。第一个为 1024px 的屏幕宽度调整 HTML 元素的大小。第二个为 316px 的屏幕宽度调整它们的大小。这是在三个样式表中设置网格字母大小的方式。

Style Sheets

样式表比较

所有尺寸的样式设计都在 Guess WordFull 和 Guess WordBase 中完成。任何其他不需要缩放的样式(包括尺寸)都在 Guess Word.css 中完成。两个尺寸样式表必须完全对齐。这是创建它们的程序强制的约束。创建它们的程序是一个简单的 C# 程序,它读取两个尺寸样式表并创建名为 Guess Word312.css 到 Guess Word1028.css 的样式表。该程序的源代码包含在下载文件中。用户界面看起来是这样的

Style Sheets

CSS 缩放器

 

当浏览器加载 Guess Word.HTML 网页时,最先发生的是 HTML 标题中的样式表引用被切换,以匹配浏览器窗口的大小。

    
        $(document).ready(function () {
            //
            // Load stylesheet based on screen size
            LoadSizingStyleSheet();
        ...
            const LoadSizingStyleSheet = function () {
                let screensize = $(window).width();
                let size = "Full";
                for (let screensizes = 320; screensizes <= 1024; screensizes += 4) {
                    if (screensize <= screensizes) {
                        size = screensizes;
                        break;
                    }
                }
                let version = $("#version").text().trim();
                if (version != "") {
                    version = "_" + version.replace(".", "_");
                }

                $("#screensize").attr("href", "css/GuessWord" + size + version + ".css");
                setTimeout(function () {
                    SetGameName();
                }, 100);
            }

请注意,Guess Word 中的 JavaScript 和 CSS 文件具有版本号,以确保在服务器版本更新时游戏也得到更新。

如果网页被调整大小,CSS 文件会被更新。这在 resize 事件中完成。

        $(window).resize(function () {
            LoadSizingStyleSheet();
        });

部署

我们需要一种方法将 HTML、CSS、JS 和 IMG 文件复制到服务器。对于简单的应用程序,这通常通过文件复制或 FTP 传输来完成。然而,浏览器喜欢缓存它们之前看到过的文件,并且让浏览器实际返回服务器并获取最新文件需要用户知道如何清除缓存并进行硬重置。为了解决这个问题,我为每个 JavaScript (.js) 和样式表 (.ccs) 文件添加了一个版本号。我编写了一个简单的 C# 程序来将当前版本号插入 HTML 文件,然后将该版本号附加到构成应用程序的每个文件。第三方文件,如 W3.css 和 jQuery 分发版,是从外部源加载的,不包含在版本控制过程中。C# 程序获取应用程序文件,对其进行版本化,并将它们写入一个名为“Deploy”的单独文件夹。然后可以从 Deploy 文件夹将应用程序文件复制或 FTP 到 Web 服务器。该程序的源代码包含在源代码下载中。用户界面看起来是这样的

GuessWord Why & How

W3

我想要一个可以同时适用于移动设备和大屏幕的简单框架。我选择 W3 而不是 BootStrap 等替代品,因为它看起来更简单,并且完全基于 CSS。我使用它的类来部分样式化网页上的大多数元素。菜单几乎完全依赖于 W3.css。

            <div id="menu" class="w3-bar w3-show">
                <a id="play" href="javascript: StartGame();" class="w3-bar-item w3-button w3-left-align whitetext">🎬Play</a>
                <a id="about" href="javascript: ShowAbout();" class="w3-bar-item w3-button w3-left-align whitetext">🧐About</a>
                <a id="history" href="javascript: ShowHistory();" class="w3-bar-item w3-button w3-left-align whitetext">📜History</a>
                <div class="w3-dropdown-hover">
                    <button class="w3-button whitetext" onclick="ShowDropDownBody('gamesdropdown');">🎲Games▾</button>
                    <div id="gamesdropdown" class="w3-dropdown-content w3-bar-block w3-card-4 greengrad whitetext">
                        <a id="link1" href="javascript: SetGridWidth(page_fourlettergame, normalMode);" class="w3-bar-item w3-button">4-letter game</a>
                        <a id="link2" href="javascript: SetGridWidth(page_fivelettergame, normalMode);" class="w3-bar-item w3-button">5-letter game</a>
                        <a id="link3" href="javascript: SetGridWidth(page_sixlettergame, normalMode);" class="w3-bar-item w3-button">6-letter game</a>
                        <a id="link3" href="javascript: SetGridWidth(page_fivelettergame, dailyMode);" class="w3-bar-item w3-button">Daily challenge</a>
                    </div>
                </div>
            </div>

我无法让 W3 实现从移动到桌面的自动尺寸调整,但我确实没有为此付出太多努力。如果我从一开始就使用 W3,我会取得更大的成功。

更多游戏

我使用 Guess Word 作为基础,又创建了两个原创单词游戏。

Griddle

Griddle 显示一个 5x5 的网格,其中填充了一些字母。目标是完成网格,使每一行单词与每一列单词匹配。这出奇地困难。起始位置和获胜位置显示在这两个屏幕截图中。

Griddle Starting Position

Griddle Winning Game

Anagram Pyramid

Anagram Pyramid 显示一个字母金字塔。顶部两行已填充。目标是通过从前一行的字母加上一个额外的字母创建字谜来完成金字塔。起始位置和获胜位置显示在这两个屏幕截图中。

Anagram Pyramid Starting Game

Anagram Pyramid Winning Game

结论和关注点

虽然 Guess Word 看起来是 Wordle 的克隆,但它是一个使用 CSS、HTML、W3.css 和 jQuery 从头开始开发的原创应用程序。我使用 Visual Studio 2022 作为开发平台,并编写了几个支持程序来支持从廉价智能手机到桌面 PC 的各种屏幕尺寸。

Guess Word 取消了 Wordle 的每天一次的限制,并增加了 4 字母和 6 字母的版本。它也是服务器独立的。一旦 Guess Word 网页加载完毕,用户就可以在没有互联网连接的情况下继续玩。我和我的妻子每天早上都会玩 Wordle,看看谁能获得更低的分数。玩 Guess Word 磨练了我们的技能,所以我们在 Wordle 中很少输。

Guess Word 展示了一种在不使用外部数据库或服务器的情况下将字典包含到单词游戏中的简单方法。它展示了如何使用 HTML 和 CSS 为单词游戏创建游戏棋盘。它使用 JavaScript 和 jQuery 来实现游戏逻辑。虽然它不是动作游戏,但 Guess Word 使用 CSS 动画来制作一些动作,例如翻转和重新着色字母磁贴和键盘按键。

该应用程序可以作为类似游戏(如 Scrabble 和 Boggle)的基础。例如,您可以使用大部分代码来创建基于 Web 的 Scrabble 和 Boggle 实现。

© . All rights reserved.