تمرین و پروژه‌های پایتون

ساخت شبیه‌ساز تاس با پایتون؛ پروژه کامل با random، چند تاس و آمار نتایج

ساخت شبیه‌ساز تاس با پایتون

🗓 آخرین به‌روزرسانی:

ساخت شبیه‌ساز تاس با پایتون یکی از پروژه‌های ساده اما بسیار مفید برای تمرین تولید عدد تصادفی، حلقه‌ها، توابع، لیست‌ها و مدیریت ورودی کاربر است. در این آموزش فقط یک عدد بین ۱ تا ۶ چاپ نمی‌کنیم؛ برنامه‌ای می‌سازیم که بتواند یک یا چند تاس را هم‌زمان پرتاب کند، تعداد وجه‌های تاس را تغییر دهد، نتیجه هر تاس را نمایش دهد، مجموع و میانگین را محاسبه کند و در پایان امکان اجرای دوباره برنامه را در اختیار کاربر قرار دهد.

این مقاله هشتمین پروژه فاز اول دسته تمرین و پروژه‌های پایتون است. اگر پروژه‌های قبلی مثل بازی حدس عدد با پایتون یا سنگ کاغذ قیچی با پایتون را انجام داده باشید، با ماژول random و تولید انتخاب تصادفی آشنایی اولیه دارید. در این پروژه همان مفهوم را به شکل منظم‌تر و قابل توسعه‌تر استفاده می‌کنیم.

در این پروژه چه چیزی می‌سازیم؟

  • شبیه‌سازی پرتاب یک تاس معمولی شش‌وجهی
  • امکان انتخاب تعداد تاس‌ها
  • امکان انتخاب تعداد وجه‌های هر تاس
  • تولید نتیجه تصادفی برای هر تاس
  • نمایش نتیجه هر تاس به‌صورت جداگانه
  • محاسبه مجموع نتایج
  • محاسبه کمترین و بیشترین مقدار
  • محاسبه میانگین نتایج
  • اعتبارسنجی ورودی کاربر
  • امکان اجرای چندباره بدون بستن برنامه

پیش‌نیازهای پروژه

برای این پروژه نیازی به نصب کتابخانه خارجی نداریم. ماژول random بخشی از کتابخانه استاندارد پایتون است. اگر با مفهوم ماژول‌ها آشنایی ندارید، مقاله ماژول‌ها و پکیج‌ها در پایتون را مرور کنید.

یک فایل جدید با نام زیر ایجاد کنید:

dice_simulator.py

سپس برنامه را با دستور زیر اجرا کنید:

python dice_simulator.py

مرحله اول: ساده‌ترین شبیه‌ساز تاس

یک تاس استاندارد شش وجه دارد و نتیجه هر پرتاب باید یکی از اعداد ۱ تا ۶ باشد. ساده‌ترین راه برای شبیه‌سازی آن استفاده از random.randint() است:

import random

result = random.randint(1, 6)
print(result)

تابع randint(a, b) یک عدد صحیح تصادفی در بازه بسته a تا b تولید می‌کند. یعنی هر دو عدد ابتدا و انتهای بازه امکان انتخاب شدن دارند.

randint چه تفاوتی با randrange دارد؟

برای این پروژه randint(1, 6) خواناتر است، چون مستقیماً می‌گوید عددی از ۱ تا ۶ تولید شود. معادل تقریبی آن با randrange() چنین است:

random.randrange(1, 7)

در randrange مقدار انتهایی داخل بازه نیست، بنابراین برای رسیدن به ۶ باید مقدار پایان را ۷ قرار دهیم.

مرحله دوم: ساخت تابع roll_dice

به‌جای اینکه منطق تولید عدد تصادفی را در بخش‌های مختلف برنامه تکرار کنیم، آن را داخل یک تابع قرار می‌دهیم:

def roll_die(sides=6):
    return random.randint(1, sides)

پارامتر sides مقدار پیش‌فرض ۶ دارد. بنابراین:

print(roll_die())

یک تاس شش‌وجهی را شبیه‌سازی می‌کند، اما این دستور:

print(roll_die(20))

می‌تواند یک تاس ۲۰ وجهی را شبیه‌سازی کند. همین تصمیم ساده باعث می‌شود پروژه فقط محدود به یک تاس استاندارد نباشد.

اعتبارسنجی تعداد وجه‌های تاس

تاسی با یک وجه یا صفر وجه منطقی نیست. بهتر است خود تابع از داده نامعتبر جلوگیری کند:

def roll_die(sides=6):
    if sides < 2:
        raise ValueError(
            "تعداد وجه‌های تاس باید حداقل 2 باشد."
        )

    return random.randint(1, sides)

مرحله سوم: پرتاب چند تاس

برای پرتاب چند تاس می‌توانیم از حلقه استفاده کنیم:

results = []

for _ in range(3):
    results.append(
        random.randint(1, 6)
    )

print(results)

اگر سه تاس پرتاب شود، خروجی ممکن است چیزی شبیه این باشد:

[4, 2, 6]

چون نتیجه‌ها داخل یک لیست ذخیره شده‌اند، می‌توانیم بعداً مجموع، میانگین، کمترین و بیشترین مقدار را محاسبه کنیم.

نسخه بهتر تابع برای چند تاس

def roll_dice(
    count=1,
    sides=6,
):
    if count < 1:
        raise ValueError(
            "تعداد تاس‌ها باید حداقل 1 باشد."
        )

    if sides < 2:
        raise ValueError(
            "تعداد وجه‌ها باید حداقل 2 باشد."
        )

    return [
        random.randint(1, sides)
        for _ in range(count)
    ]

در این نسخه از List Comprehension استفاده شده است. برای مثال:

print(
    roll_dice(
        count=2,
        sides=6,
    )
)

دو تاس شش‌وجهی تولید می‌کند.

مرحله چهارم: نمایش مرتب نتایج

بهتر است فقط خود لیست را چاپ نکنیم. هر نتیجه را همراه شماره تاس نمایش می‌دهیم:

def show_results(results):
    print("\nنتایج پرتاب:")

    for index, value in enumerate(
        results,
        start=1,
    ):
        print(
            f"تاس {index}: {value}"
        )

اگر خروجی [4, 2, 6] باشد:

نتایج پرتاب:
تاس 1: 4
تاس 2: 2
تاس 3: 6

مرحله پنجم: محاسبه آمار نتایج

وقتی چند تاس داریم، نمایش چند شاخص ساده می‌تواند پروژه را آموزنده‌تر کند:

def calculate_stats(results):
    total = sum(results)
    minimum = min(results)
    maximum = max(results)
    average = total / len(results)

    return {
        "total": total,
        "minimum": minimum,
        "maximum": maximum,
        "average": average,
    }

حالا اگر نتیجه‌ها [2, 5, 6] باشند:

  • مجموع: ۱۳
  • کمترین مقدار: ۲
  • بیشترین مقدار: ۶
  • میانگین: حدود ۴.۳۳

نمایش آمار

def show_stats(results):
    stats = calculate_stats(results)

    print("\nآمار:")
    print(
        f"مجموع: {stats['total']}"
    )
    print(
        f"کمترین: {stats['minimum']}"
    )
    print(
        f"بیشترین: {stats['maximum']}"
    )
    print(
        f"میانگین: {stats['average']:.2f}"
    )

مرحله ششم: دریافت عدد معتبر از کاربر

کاربر ممکن است متن، مقدار منفی یا عدد صفر وارد کند. یک تابع عمومی برای دریافت عدد صحیح می‌سازیم:

def get_positive_int(
    message,
    minimum=1,
    maximum=None,
):
    while True:
        value = input(message).strip()

        try:
            number = int(value)
        except ValueError:
            print(
                "لطفاً یک عدد صحیح وارد کنید."
            )
            continue

        if number < minimum:
            print(
                f"مقدار باید حداقل {minimum} باشد."
            )
            continue

        if (
            maximum is not None
            and number > maximum
        ):
            print(
                f"مقدار باید حداکثر {maximum} باشد."
            )
            continue

        return number

این تابع در پروژه‌های دیگر هم قابل استفاده است، چون حداقل و حداکثر را از طریق پارامتر دریافت می‌کند.

چرا برای تعداد تاس محدودیت می‌گذاریم؟

از نظر فنی کاربر می‌تواند تعداد بسیار بزرگی تاس درخواست کند، اما برای یک برنامه تعاملی ترمینال بهتر است تعداد را محدود کنیم تا خروجی بیش از حد طولانی نشود. در این پروژه حداکثر تعداد را ۲۰ در نظر می‌گیریم.

مرحله هفتم: اجرای یک دور کامل

def run_round():
    count = get_positive_int(
        "تعداد تاس‌ها (1 تا 20): ",
        minimum=1,
        maximum=20,
    )

    sides = get_positive_int(
        "تعداد وجه‌های هر تاس (2 تا 100): ",
        minimum=2,
        maximum=100,
    )

    results = roll_dice(
        count=count,
        sides=sides,
    )

    show_results(results)
    show_stats(results)

مرحله هشتم: امکان اجرای دوباره برنامه

برای اینکه بعد از یک پرتاب برنامه بسته نشود، از حلقه while استفاده می‌کنیم:

def main():
    print(
        "=== شبیه‌ساز تاس با پایتون ==="
    )

    while True:
        run_round()

        again = input(
            "\nدوباره تاس بریزیم؟ (بله/خیر): "
        ).strip()

        if again != "بله":
            print("برنامه پایان یافت.")
            break

کد کامل شبیه‌ساز تاس با پایتون

import random


def roll_dice(
    count=1,
    sides=6,
):
    if count < 1:
        raise ValueError(
            "تعداد تاس‌ها باید حداقل 1 باشد."
        )

    if sides < 2:
        raise ValueError(
            "تعداد وجه‌ها باید حداقل 2 باشد."
        )

    return [
        random.randint(1, sides)
        for _ in range(count)
    ]


def calculate_stats(results):
    if not results:
        raise ValueError(
            "لیست نتایج نباید خالی باشد."
        )

    total = sum(results)

    return {
        "total": total,
        "minimum": min(results),
        "maximum": max(results),
        "average": total / len(results),
    }


def show_results(results):
    print("\nنتایج پرتاب:")

    for index, value in enumerate(
        results,
        start=1,
    ):
        print(
            f"تاس {index}: {value}"
        )


def show_stats(results):
    stats = calculate_stats(results)

    print("\nآمار:")
    print(
        f"مجموع: {stats['total']}"
    )
    print(
        f"کمترین: {stats['minimum']}"
    )
    print(
        f"بیشترین: {stats['maximum']}"
    )
    print(
        f"میانگین: {stats['average']:.2f}"
    )


def get_positive_int(
    message,
    minimum=1,
    maximum=None,
):
    while True:
        value = input(message).strip()

        try:
            number = int(value)
        except ValueError:
            print(
                "لطفاً یک عدد صحیح وارد کنید."
            )
            continue

        if number < minimum:
            print(
                f"مقدار باید حداقل {minimum} باشد."
            )
            continue

        if (
            maximum is not None
            and number > maximum
        ):
            print(
                f"مقدار باید حداکثر {maximum} باشد."
            )
            continue

        return number


def run_round():
    count = get_positive_int(
        "تعداد تاس‌ها (1 تا 20): ",
        minimum=1,
        maximum=20,
    )

    sides = get_positive_int(
        "تعداد وجه‌های هر تاس (2 تا 100): ",
        minimum=2,
        maximum=100,
    )

    results = roll_dice(
        count=count,
        sides=sides,
    )

    show_results(results)
    show_stats(results)


def main():
    print(
        "=== شبیه‌ساز تاس با پایتون ==="
    )

    while True:
        run_round()

        again = input(
            "\nدوباره تاس بریزیم؟ (بله/خیر): "
        ).strip()

        if again != "بله":
            print("برنامه پایان یافت.")
            break


if __name__ == "__main__":
    main()

نمونه اجرای برنامه

=== شبیه‌ساز تاس با پایتون ===
تعداد تاس‌ها (1 تا 20): 3
تعداد وجه‌های هر تاس (2 تا 100): 6

نتایج پرتاب:
تاس 1: 4
تاس 2: 2
تاس 3: 6

آمار:
مجموع: 12
کمترین: 2
بیشترین: 6
میانگین: 4.00

طبیعی است که در هر اجرا نتیجه متفاوت باشد، چون random.randint() هر بار مقدار جدیدی تولید می‌کند.

آیا random واقعاً تصادفی است؟

ماژول random از مولد شبه‌تصادفی استفاده می‌کند. این روش برای بازی، شبیه‌سازی، تمرین برنامه‌نویسی و پروژه تاس مناسب است، اما برای رمز عبور و داده‌های امنیتی گزینه مناسبی نیست. در مقاله ساخت تولیدکننده رمز عبور با پایتون به همین دلیل از secrets استفاده کردیم.

استفاده از seed برای تست

در برنامه واقعی معمولاً نیازی به تنظیم Seed نداریم، اما هنگام تست می‌توانیم خروجی تصادفی را قابل تکرار کنیم:

random.seed(42)

print(
    roll_dice(3, 6)
)

وقتی Seed یکسان باشد، دنباله تولیدشده نیز قابل تکرار می‌شود. این ویژگی برای تست خودکار مفید است.

نسخه گرافیکی تاس در ترمینال

برای تاس شش‌وجهی می‌توانیم برای هر عدد یک طرح ASCII تعریف کنیم و به‌جای نمایش عدد ساده، شکل تاس را چاپ کنیم. نمونه برای عدد ۵:

┌───────┐
│ ●   ● │
│   ●   │
│ ●   ● │
└───────┘

برای پیاده‌سازی کامل می‌توانید یک دیکشنری با کلیدهای ۱ تا ۶ بسازید و شکل هر تاس را به‌صورت Tuple از رشته‌ها ذخیره کنید.

محاسبه احتمال در شبیه‌سازی

در یک تاس شش‌وجهی منصفانه، احتمال نظری هر عدد برابر با یک‌ششم است. می‌توانید برنامه را توسعه دهید تا مثلاً ۱۰ هزار بار تاس را پرتاب کند و تعداد تکرار هر عدد را بشمارد. با افزایش تعداد آزمایش‌ها، نسبت نتایج معمولاً به احتمال نظری نزدیک‌تر می‌شود.

counts = {
    number: 0
    for number in range(1, 7)
}

for _ in range(10000):
    value = random.randint(1, 6)
    counts[value] += 1

print(counts)

این تمرین راه خوبی برای ورود به مباحث شبیه‌سازی و احتمال با پایتون است.

اشتباه‌های رایج در پروژه شبیه‌ساز تاس

استفاده از randint(0, 6)

تاس استاندارد عدد صفر ندارد. بازه صحیح باید از ۱ شروع شود.

استفاده از randrange(1, 6)

در این حالت عدد ۶ تولید نمی‌شود، چون مقدار پایان در randrange خارج از بازه است. باید از randrange(1, 7) استفاده کنید.

اعتماد مستقیم به input

ورودی کاربر رشته است و ممکن است متن یا مقدار منفی باشد. تبدیل و اعتبارسنجی باید قبل از ورود به منطق اصلی انجام شود.

نوشتن یک تابع بسیار بزرگ

اگر دریافت ورودی، تولید تاس، محاسبه آمار و نمایش نتیجه همه داخل یک تابع باشند، تست و توسعه برنامه سخت‌تر می‌شود. در این پروژه هر مسئولیت در تابع مستقل قرار گرفته است.

چطور پروژه را توسعه دهیم؟

  1. نمایش ASCII: برای تاس‌های ۱ تا ۶ طرح گرافیکی متنی بسازید.
  2. تاریخچه پرتاب‌ها: نتایج هر دور را داخل یک لیست ذخیره کنید.
  3. ذخیره در فایل: تاریخچه را در JSON یا فایل متنی نگه دارید.
  4. حالت دو بازیکن: هر بازیکن دو تاس بریزد و مجموع بزرگ‌تر برنده شود.
  5. شمارنده دفعات: تعداد کل پرتاب‌های انجام‌شده را نمایش دهید.
  6. آمار فراوانی: تعداد تکرار هر وجه را در صد یا هزار پرتاب محاسبه کنید.
  7. رابط گرافیکی: با Tkinter دکمه «پرتاب تاس» و تصویر هر وجه را نمایش دهید.
  8. انیمیشن: قبل از نتیجه نهایی چند مقدار سریع نمایش دهید تا حس پرتاب ایجاد شود.

تمرین پیشنهادی

نسخه‌ای از برنامه بسازید که دو تاس شش‌وجهی را ۱۰۰۰ بار پرتاب کند و مشخص کند مجموع‌های ۲ تا ۱۲ هر کدام چند بار رخ داده‌اند. در پایان بررسی کنید کدام مجموع بیشترین تکرار را داشته است.

برای شروع:

totals = {
    value: 0
    for value in range(2, 13)
}

for _ in range(1000):
    dice = roll_dice(2, 6)
    totals[sum(dice)] += 1

print(totals)

این پروژه چه مفاهیمی را تمرین می‌دهد؟

  • ماژول random
  • تابع randint
  • توابع و پارامترهای پیش‌فرض
  • List Comprehension
  • حلقه for و while
  • enumerate
  • List و Dictionary
  • توابع sum، min، max و len
  • اعتبارسنجی ورودی
  • Exception Handling
  • طراحی تابع‌های کوچک و قابل تست
  • مبانی شبیه‌سازی و احتمال

جمع‌بندی

در این آموزش یک شبیه‌ساز تاس با پایتون ساختیم که فقط به یک تاس شش‌وجهی محدود نیست. کاربر می‌تواند تعداد تاس‌ها و تعداد وجه‌ها را انتخاب کند، برنامه نتایج را با random.randint() تولید می‌کند و سپس مجموع، کمترین، بیشترین و میانگین را نمایش می‌دهد.

ساختار پروژه طوری طراحی شده که بتوانید بدون بازنویسی کامل برنامه قابلیت‌هایی مثل تاریخچه، ASCII Art، آمار فراوانی و حالت چندبازیکنه را اضافه کنید. برای یادگیری بهتر، تمرین شبیه‌سازی ۱۰۰۰ پرتاب دو تاس را انجام دهید و توزیع مجموع‌ها را بررسی کنید.

📌 همچنین بخوانید: تمرین پایتون با جواب، ساخت بازی با پایتون و Pygame و ساخت ربات با پایتون

می‌خواهی پیشرفتت ذخیره شود؟با ورود یا ثبت‌نام رایگان، درس‌های تمام‌شده را علامت بزن و درصد پیشرفتت را در حساب کاربری ببین.ورود / ثبت‌نام برای ذخیره پیشرفت
درس بعدی · پروژه‌های پایتون (9 از 19)ساخت لیست کارها با پایتون؛ پروژه کامل Todo List با ذخیره در JSON ←
آموزش‌های تازه، کد آماده و رفع خطاهر هفته در کانال Dev Source؛ رایگان.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *