Engee 文档
Notebook

表情点击器

在编程领域,创建小型但成熟的游戏是磨练技能和探索新技术的好方法。 其中一个项目是Emoji Clicker,这是一款网络游戏,玩家点击emojis以增加计数器。 尽管它表面上很简单,但在这个游戏的引擎盖下有几种技术的强大共生:后端高性能的Julia语言,前端经典的HTML/CSS/JavaScript,以及数据安全的加密方法。

这个游戏不仅仅是一个唱首歌。 这是现代开发方法的一个例子,其中服务器逻辑,图形和用户交互从单个Julia代码库创建和管理,模糊了传统桌面编程和web开发之间的界限。

游戏的目标简单而令人上瘾:您需要点击屏幕中心的表情符号。 每次点击都会增加计数器。 为了使这个过程在视觉上有趣,表情符号每100次点击就会改变一次,经历了从微笑的笑脸到独角兽的整个演变。 游戏还包括一个完整的进度保存系统,您的成就(点击次数,当前头像,时间)被加密成一个特殊的密钥,可以复制并稍后输入,以便从同一个地方继续游戏。

让我们继续实现,我们将需要以下库:

  • using Base64-用于编码和解码Base64格式的数据。 需要将二进制数据(您的保存)转换为可以轻松复制或输入的文本字符串。

  • using Dates-用于处理日期和时间。 需要根据当前时间生成唯一标识符,并为保存添加时间戳。

In [ ]:
using Base64, Dates

现在让我们声明项目的关键常量:

  • **const EMOJIS**是将在游戏中使用的表情符号的数组。 每个元素都是玩家的视觉化身,随着你的进步而改变。

  • const EMOJI_CHANGE_THRESHOLD = 100-这是表情符号更改阈值。 电话号码100 确定您需要键入多少次点击才能将当前表情符号更改为数组中的下一个表情符号。 这创建了一个奖励系统和视觉进度,激励用户进一步点击。

In [ ]:
const EMOJIS = ["😀", "😎", "🤩", "👨‍🚀", "🐘", "🙇‍♂️", "🐭", "🐆", "🐵", "🦄"]
const EMOJI_CHANGE_THRESHOLD = 100
WARNING: redefinition of constant Main.EMOJIS. This may fail, cause incorrect answers, or produce other errors.
Out[0]:
100

功能 encrypt_data —这是一个自定义编码器,以保护游戏的保存数据。 它接受源数据(data)以字符串和数字键的形式(key)默认情况下 0x55 之后,函数处理字符串的每个字节:XOR():使用"独占或"操作在字节上施加密钥,位移:"混洗"加密字节的位,将它们向左移动2个位置(<< 2)并结合向右移动6个位置的结果(>> 6接下来,该函数对生成的一组字节进行编码,并将其转换为Base64编码的文本字符串,以便可以轻松复制或输入。

In [ ]:
function encrypt_data(data::String, key::UInt8=0x55)::String
    encrypted = Vector{UInt8}()
    for byte in Vector{UInt8}(data)
        encrypted_byte = (byte  key) << 2 | (byte  key) >> 6
        push!(encrypted, encrypted_byte)
    end
    return base64encode(encrypted)
end
Out[0]:
encrypt_data (generic function with 2 methods)

**decrypt_data**是反函数为 encrypt_data. 它对数据进行解密,算法简述如下。:

  1. 将Base64中的字符串解码回字节。
  2. 对每个字节执行反向位移位。: (byte >> 2) | (byte << 6).
  3. 应用具有相同键的XOR操作(0x55)来返回原始字节。
  4. 将所有字节收集到一个字符串中并返回它。
In [ ]:
function decrypt_data(encrypted_data::String, key::UInt8=0x55)::String
    decoded = base64decode(encrypted_data)
    decrypted = Vector{UInt8}()
    for byte in decoded
        decrypted_byte = (byte >> 2) | (byte << 6)
        push!(decrypted, decrypted_byte  key)
    end
    return String(decrypted)
end
Out[0]:
decrypt_data (generic function with 2 methods)

**功能 create_clicker_interface**是整个应用程序的核心。 它的主要任务是在浏览器中动态生成并显示一个完整的游戏界面。 为此,它会创建一个包含所有必要的HTML,CSS和JavaScript代码的大字符串。 为了确保游戏的多个实例不会相互冲突,函数会生成一个唯一标识符,该标识符嵌入到所有元素和函数的名称中。 这确保了逻辑的隔离和正确操作。

嵌入式JavaScript代码负责客户端的所有交互性。 功能 handleClick 处理表情符号上的每次点击:增加计数器,播放光压缩动画,并检查是否是时候根据达到的点击阈值将玩家的头像更改为下一个头像。 这是纯粹的游戏机制。

保存系统通过功能控制的下拉菜单实现 toggleDropdown. 功能 generateKey 收集所有玩家的数据(当前得分,表情符号索引和时间戳),从中形成一个字符串并在Base64中编码,创建相同的"保存密钥"。 功能 loadKey 执行反向操作:它解码用户输入的密钥,从中提取数据并恢复游戏状态,允许您从中断的地方继续。

In [ ]:
function create_clicker_interface()
    println("🎮 Запуск Emoji Clicker!")
    println("👇 Кликайте на эмоджи в центре!")
    unique_id = string(hash(now()), rand(1000:9999))
    html_interface = """
    <div style="font-family: 'Arial', sans-serif; text-align: center; padding: 40px 20px; 
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 
                min-height: 100vh; color: white;">
        <h1 style="font-size: 3em; margin-bottom: 30px; text-shadow: 2px 2px 4px rgba(0,0,0,0.5);">
            ✨ Emoji Clicker ✨
        </h1>
        <div id="counter_$(unique_id)" 
             style="font-size: 4em; font-weight: bold; margin: 30px 0; 
                    background: rgba(255,255,255,0.1); padding: 20px; 
                    border-radius: 15px; display: inline-block; min-width: 200px;">
            0
        </div>
        <div id="emoji_$(unique_id)" 
             style="font-size: 8em; cursor: pointer; margin: 40px 0; 
                    transition: all 0.2s ease; user-select: none; 
                    text-shadow: 4px 4px 8px rgba(0,0,0,0.4);
                    animation: float_$(unique_id) 3s ease-in-out infinite;">
            $(EMOJIS[1])
        </div>
        <div style="position: relative; display: inline-block;">
            <button id="saveBtn_$(unique_id)" 
                    style="background: rgba(255,255,255,0.2); border: 3px solid rgba(255,255,255,0.5); 
                           color: white; padding: 15px 30px; border-radius: 50px; 
                           cursor: pointer; font-size: 1.2em; margin: 20px 0;
                           transition: all 0.3s ease; font-weight: bold;">
                💾 Сохранения
            </button>
            <div id="saveDropdown_$(unique_id)" 
                 style="display: none; position: absolute; top: 100%; left: 0; 
                        background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%); 
                        padding: 20px; border-radius: 15px; margin-top: 10px; 
                        min-width: 300px; z-index: 1000; box-shadow: 0 8px 25px rgba(0,0,0,0.3);">
                <h3 style="margin-bottom: 15px; color: white;">💾 Управление сохранениями</h3>
                <div style="margin-bottom: 15px;">
                    <button onclick="generateKey_$(unique_id)()" 
                            style="background: #27ae60; color: white; border: none; 
                                   padding: 10px 20px; border-radius: 8px; cursor: pointer;
                                   font-size: 0.9em; font-weight: bold; width: 100%;">
                        🔑 Создать новое сохранение
                    </button>
                </div>
                <div id="keyDisplay_$(unique_id)" 
                     style="background: rgba(255,255,255,0.1); padding: 12px; 
                            border-radius: 10px; margin-bottom: 15px; display: none;">
                    <div style="font-size: 0.9em; margin-bottom: 8px;">Ключ сохранения:</div>
                    <div id="keyValue_$(unique_id)" style="word-break: break-all; font-family: monospace; font-size: 0.8em;"></div>
                    <button onclick="copyKey_$(unique_id)()" 
                            style="background: #3498db; color: white; border: none; 
                                   padding: 6px 12px; border-radius: 5px; cursor: pointer; 
                                   margin-top: 8px; font-size: 0.8em;">
                        📋 Копировать
                    </button>
                </div>
                <div style="margin-bottom: 15px;">
                    <input type="text" id="loadKeyInput_$(unique_id)" 
                           placeholder="Введите ключ сохранения" 
                           style="padding: 10px; border-radius: 8px; border: none; 
                                  width: 100%; margin-bottom: 10px; font-size: 0.9em;">
                    <button onclick="loadKey_$(unique_id)()" 
                            style="background: #e67e22; color: white; border: none; 
                                   padding: 10px 20px; border-radius: 8px; cursor: pointer;
                                   font-size: 0.9em; font-weight: bold; width: 100%;">
                        🎮 Загрузить сохранение
                    </button>
                </div>
                <div id="loadResult_$(unique_id)" 
                     style="background: rgba(255,255,255,0.1); padding: 12px; 
                            border-radius: 10px; margin-bottom: 15px; display: none;">
                    <div style="font-size: 0.9em; margin-bottom: 8px;">Данные сохранения:</div>
                    <div id="decryptedData_$(unique_id)" style="word-break: break-all; font-size: 0.8em;"></div>
                </div>
            </div>
        </div>
        <style>
            @keyframes float_$(unique_id) {
                0%, 100% { transform: translateY(0px); }
                50% { transform: translateY(-10px); }
            }
        </style>
    </div>
    <script>
    let clickCount_$(unique_id) = 0;
    let dropdownVisible_$(unique_id) = false;
    const emojis_$(unique_id) = $(EMOJIS);
    const threshold_$(unique_id) = $(EMOJI_CHANGE_THRESHOLD);
    
    function handleClick_$(unique_id)() {
        clickCount_$(unique_id)++;
        const counterElement = document.getElementById('counter_$(unique_id)');
        counterElement.textContent = clickCount_$(unique_id);
        const emojiElement = document.getElementById('emoji_$(unique_id)');
        emojiElement.style.transform = 'scale(0.8)';
        setTimeout(() => {
            emojiElement.style.transform = 'scale(1)';
        }, 100);
        if (clickCount_$(unique_id) % threshold_$(unique_id) === 0) {
            const emojiIndex = Math.min(
                Math.floor(clickCount_$(unique_id) / threshold_$(unique_id)) % emojis_$(unique_id).length, 
                emojis_$(unique_id).length - 1
            );
            emojiElement.textContent = emojis_$(unique_id)[emojiIndex];
        }
    }
    
    function toggleDropdown_$(unique_id)() {
        dropdownVisible_$(unique_id) = !dropdownVisible_$(unique_id);
        document.getElementById('saveDropdown_$(unique_id)').style.display = dropdownVisible_$(unique_id) ? 'block' : 'none';
    }
    
    function generateKey_$(unique_id)() {
        const timestamp = new Date().toISOString();
        const emojiIndex = Math.min(Math.floor(clickCount_$(unique_id) / threshold_$(unique_id)) % emojis_$(unique_id).length, emojis_$(unique_id).length - 1);
        const data = "Count:" + clickCount_$(unique_id) + "|Time:" + timestamp + "|Emoji:" + emojiIndex;
        const keyDisplay = document.getElementById('keyDisplay_$(unique_id)');
        const keyValue = document.getElementById('keyValue_$(unique_id)');
        keyValue.textContent = btoa(data);
        keyDisplay.style.display = 'block';
    }
    
    function copyKey_$(unique_id)() {
        const keyText = document.getElementById('keyValue_$(unique_id)').textContent;
        navigator.clipboard.writeText(keyText).then(() => {
            alert('Ключ скопирован!');
        });
    }

    function loadKey_$(unique_id)() {
        const keyInput = document.getElementById('loadKeyInput_$(unique_id)').value;
        const resultDiv = document.getElementById('loadResult_$(unique_id)');
        const decryptedDiv = document.getElementById('decryptedData_$(unique_id)');
        try {
            const decodedData = atob(keyInput);
            decryptedDiv.textContent = decodedData;
            resultDiv.style.display = 'block';
            const countMatch = decodedData.match(/Count:(\\d+)/);
            const emojiMatch = decodedData.match(/Emoji:(\\d+)/);
            if (countMatch && emojiMatch) {
                clickCount_$(unique_id) = parseInt(countMatch[1]);
                const emojiIndex = parseInt(emojiMatch[1]);
                document.getElementById('counter_$(unique_id)').textContent = clickCount_$(unique_id);
                document.getElementById('emoji_$(unique_id)').textContent = emojis_$(unique_id)[emojiIndex];
                alert('Сохранение успешно загружено!');
                document.getElementById('loadKeyInput_$(unique_id)').value = '';
            } else {
                alert('Ошибка: неверный формат данных в ключе');
            }
        } catch (e) {
            decryptedDiv.textContent = 'Ошибка: неверный формат ключа';
            resultDiv.style.display = 'block';
            alert('Ошибка при загрузке сохранения: неверный формат ключа');
        }
    }
    document.getElementById('emoji_$(unique_id)').onclick = handleClick_$(unique_id);
    document.getElementById('saveBtn_$(unique_id)').onclick = toggleDropdown_$(unique_id);
    document.addEventListener('click', function(event) {
        const dropdown = document.getElementById('saveDropdown_$(unique_id)');
        const button = document.getElementById('saveBtn_$(unique_id)');
        
        if (dropdownVisible_$(unique_id) && !dropdown.contains(event.target) && !button.contains(event.target)) {
            dropdownVisible_$(unique_id) = false;
            dropdown.style.display = 'none';
        }
    });
    </script>
    """
    display("text/html", html_interface)
end

create_clicker_interface()
🎮 Запуск Emoji Clicker!
👇 Кликайте на эмоджи в центре!

✨ Emoji Clicker ✨

0
😀

结论

总之,我们可以说这个项目是Julia和现代web技术的高性能计算的成功综合,在交互式web界面中实现了成熟的游戏机制。 关键特征是架构解决方案,其中整个复合体-从后端逻辑和加密功能到视觉上吸引人的前端的生成-被封装在单个代码库中,这表明它不仅在科学领域,而且在创建具有游戏设计和数据保护元素的实用web应用程序领域的多功能性。