هذه لعبة ثعبان تعليمية في طرفية C++17 تعمل بالأدوار: اكتب w أو a أو s أو d ثم Enter للتحرك خطوة. تجمع الطعام فتكبر ويزيد رصيد النقاط، وتنتهي اللعبة عند اصطدام الرأس بالجدار أو الجسم. تستخدم النسخة مكتبات قياسية ولا تحتاج conio.h أو windows.h أو ملف stdafx.h.
الكود الكامل باستخدام C++17
#include <algorithm>
#include <iostream>
#include <random>
#include <stdexcept>
#include <utility>
#include <vector>
class SnakeGame {
public:
using Cell = std::pair<int, int>;
private:
int width, height;
std::vector<Cell> body{{3, 3}, {2, 3}, {1, 3}};
Cell direction{1, 0};
Cell food{-1, -1};
bool alive = true, won = false;
std::mt19937 rng;
void spawnFood() {
std::vector<Cell> freeCells;
for (int y = 0; y < height; ++y)
for (int x = 0; x < width; ++x)
if (std::find(body.begin(), body.end(), Cell{x, y}) == body.end())
freeCells.emplace_back(x, y);
if (freeCells.empty()) { won = true; food = {-1, -1}; return; }
std::uniform_int_distribution<std::size_t> pick(0, freeCells.size() - 1);
food = freeCells[pick(rng)];
}
public:
explicit SnakeGame(int w = 12, int h = 8, unsigned seed = std::random_device{}())
: width(w), height(h), rng(seed) {
if (w < 4 || h < 4 || w > 40 || h > 20)
throw std::invalid_argument("Use width 4..40 and height 4..20.");
spawnFood();
}
bool running() const { return alive && !won; }
bool hasWon() const { return won; }
std::size_t score() const { return body.size() - 3; }
const std::vector<Cell>& cells() const { return body; }
Cell foodCell() const { return food; }
bool step(char command) {
if (!running()) return false;
Cell nextDirection = direction;
switch (command) {
case 'w': nextDirection = {0, -1}; break;
case 's': nextDirection = {0, 1}; break;
case 'a': nextDirection = {-1, 0}; break;
case 'd': nextDirection = {1, 0}; break;
default: return false;
}
if (nextDirection.first == -direction.first && nextDirection.second == -direction.second)
nextDirection = direction; // Ignore an immediate reverse.
direction = nextDirection;
Cell next{body.front().first + direction.first, body.front().second + direction.second};
if (next.first < 0 || next.first >= width || next.second < 0 || next.second >= height) {
alive = false; return true;
}
bool grows = next == food;
auto stop = grows ? body.end() : body.end() - 1; // Tail moves away unless growing.
if (std::find(body.begin(), stop, next) != stop) { alive = false; return true; }
body.insert(body.begin(), next);
if (grows) spawnFood();
else body.pop_back();
return true;
}
void draw(std::ostream& output) const {
output << "Score: " << score() << '\n';
for (int y = -1; y <= height; ++y) {
for (int x = -1; x <= width; ++x) {
Cell cell{x, y};
char symbol = ' ';
if (x == -1 || x == width || y == -1 || y == height) symbol = '#';
else if (cell == body.front()) symbol = 'O';
else if (std::find(body.begin(), body.end(), cell) != body.end()) symbol = 'o';
else if (cell == food) symbol = '*';
output << symbol;
}
output << '\n';
}
}
};
int main() {
SnakeGame game;
std::cout << "Turn-based Snake: enter w/a/s/d then Enter; q quits.\n";
while (game.running()) {
game.draw(std::cout);
char command{};
if (!(std::cin >> command) || command == 'q') return 0;
if (!game.step(command)) std::cout << "Use lowercase w/a/s/d.\n";
}
game.draw(std::cout);
std::cout << (game.hasWon() ? "You filled the board!\n" : "Game over.\n");
}التشغيل والتحكم
w للأعلى وs للأسفل وa لليسار وd لليمين، وq للخروج. تعرض الطرفية لوحة جديدة بعد كل أمر، دون مسح الشاشة بأوامر نظام. الحرف O يمثل الرأس وo الجسم والنجمة الطعام. الإدخال غير المعتمد لا يحرك الثعبان، ومحاولة الرجوع المباشر إلى الخلف تستمر في الاتجاه الحالي حتى لا يدخل الرأس في العنق.
تمثيل الجسم والاصطدام
يخزن vector خلايا الجسم من الرأس إلى الذيل. قبل إضافة الرأس الجديد نفحص الحدود ثم الجسم. إذا لم يأكل الثعبان، يتحرك الذيل بعيدًا، لذلك يجوز دخول الخلية التي كان فيها الذيل في هذه الخطوة. إذا أكل الطعام يبقى الذيل ويزيد الطول. يفحص الكود هذا الفرق قبل تقرير الاصطدام.
الطعام والفوز
تجمع spawnFood الخلايا الخالية وتختار واحدة منها بتوزيع منتظم. لا يظهر الطعام داخل الجسم، وعند امتلاء اللوحة تعلن اللعبة الفوز بدل الدخول في حلقة بحث لا تنتهي. يمكن تمرير seed ثابت في الاختبارات لتكرار التجربة؛ التشغيل المعتاد ينشئ مولدًا جديدًا.
حدود النسخة وتطويرها
حركة هذه النسخة مرتبطة بالإدخال وليست حركة آنية مستمرة. إضافة حركة بالوقت أو واجهة رسومية تحتاج معالجة أحداث وتوقيت تناسب البيئة المستهدفة. تكلفة البحث والرسم هنا مناسبة للوحة الصغيرة؛ ليست هذه بنية مخصصة للعبة ضخمة. ابدأ باختبار الجدار والطعام ثم حركة الذيل قبل إضافة خصائص جديدة.
ترجمة المثال وتشغيله
احفظ المثال في ملف main.cpp واختر معيار C++17 في محررك. مع GCC أو Clang يمكنك الترجمة بالأمر g++ -std=c++17 -Wall -Wextra -pedantic main.cpp -o main ثم تشغيل الملف الناتج. ترجم كل مثال كبرنامج مستقل؛ لا تجمع دوال main من دروس مختلفة في ملف واحد.






