نیما در این روزهای کرونایی حوصلهاش به شدت سر رفته. از این رو، تصمیم گرفته است تا با کمک دوست صمیمیاش، محمدرضا، یک وبسایت ساده بسازد که در آن بتوان *XO* بازی کرد. قرار است نیما *API* این بازی را طراحی کند و محمدرضا فرانت کار را پیش ببرد. اخیراً نیما درگیر امتحانات پایانترمش شده و میخواهد این وبسایت تا چند روز دیگر آماده شود. بنابراین، او از شما خواسته است تا *API* این بازی را پیادهسازی کنید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/24836/download_problem_initial_project/84145/) دانلود کنید.
<details class="grey">
<summary>ساختار فایلها</summary>
```
xo
├── app
│ ├── Console
│ │ └── Kernel.php
│ ├── Exceptions
│ │ └── Handler.php
│ ├── Http
│ │ ├── Controllers
│ │ │ ├── API
│ │ │ │ ├── AuthController.php
│ │ │ │ └── GameController.php
│ │ │ └── Controller.php
│ │ ├── Middleware
│ │ │ ├── Authenticate.php
│ │ │ ├── EncryptCookies.php
│ │ │ ├── PreventRequestsDuringMaintenance.php
│ │ │ ├── RedirectIfAuthenticated.php
│ │ │ ├── TrimStrings.php
│ │ │ ├── TrustHosts.php
│ │ │ ├── TrustProxies.php
│ │ │ └── VerifyCsrfToken.php
│ │ └── Kernel.php
│ ├── Models
│ │ ├── Game.php
│ │ ├── Move.php
│ │ └── User.php
│ └── Providers
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── BroadcastServiceProvider.php
│ ├── EventServiceProvider.php
│ └── RouteServiceProvider.php
├── bootstrap
│ ├── cache
│ │ ├── packages.php
│ │ ├── routes-v7.php
│ │ └── services.php
│ └── app.php
├── config
│ ├── app.php
│ ├── auth.php
│ ├── broadcasting.php
│ ├── cache.php
│ ├── cors.php
│ ├── database.php
│ ├── filesystems.php
│ ├── hashing.php
│ ├── logging.php
│ ├── mail.php
│ ├── queue.php
│ ├── services.php
│ ├── session.php
│ └── view.php
├── database
│ ├── factories
│ │ └── UserFactory.php
│ ├── migrations
│ │ ├── 2014_10_12_000000_create_users_table.php
│ │ ├── 2014_10_12_100000_create_password_resets_table.php
│ │ ├── 2019_08_19_000000_create_failed_jobs_table.php
│ │ ├── 2021_01_05_094004_create_games_table.php
│ │ └── 2021_01_05_095752_create_moves_table.php
│ ├── seeders
│ │ └── DatabaseSeeder.php
│ └── database.sqlite
├── public
│ ├── favicon.ico
│ ├── index.php
│ ├── robots.txt
│ └── web.config
├── resources
│ ├── css
│ │ └── app.css
│ ├── js
│ │ ├── app.js
│ │ └── bootstrap.js
│ ├── lang
│ │ └── en
│ │ ├── auth.php
│ │ ├── pagination.php
│ │ ├── passwords.php
│ │ └── validation.php
│ └── views
│ └── welcome.blade.php
├── routes
│ ├── api.php
│ ├── channels.php
│ ├── console.php
│ └── web.php
├── storage
│ ├── app
│ │ └── public
│ ├── framework
│ │ ├── cache
│ │ ├── sessions
│ │ ├── testing
│ │ └── views
│ ├── logs
│ │ └── laravel.log
│ ├── oauth-private.key
│ └── oauth-public.key
├── tests
│ ├── Feature
│ │ └── ExampleTest.php
│ ├── Unit
│ │ └── ExampleTest.php
│ ├── CreatesApplication.php
│ └── TestCase.php
├── README.md
├── artisan
├── composer.json
├── package.json
├── phpunit.xml
├── server.php
└── webpack.mix.js
```
</details>
<details class="brown">
<summary>راهاندازی پروژه</summary>
**برای اجرای پروژه، باید `php` و `composer` را از قبل نصب کرده باشید.**
+ ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
+ دستور `composer install` را در پوشهی اصلی پروژه برای نصب نیازمندیها اجرا کنید.
+ دستور `php artisan migrate` را برای پیکربندی کامل پایگاه داده اجرا کنید.
+ دستور `php artisan passport:install` را برای پیکربندی *Laravel Passport* اجرا کنید.
</details>
برای احراز هویت در این پروژه از *Laravel Passport* استفاده شده است. همچنین، برای پایگاه داده از *SQLite* استفاده شده است.
## مدلها
این پروژه شامل ۳ مدل به شرح زیر است:
1. مدل `User`:
| نام فیلد | تعریف |
|------------|--------------|
| `id` | شناسهی کاربر |
| `username` | نام کاربری |
| `email` | آدرس ایمیل |
| `password` | رمز عبور |
2. مدل `Game`:
| نام فیلد | تعریف |
|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | شناسهی بازی |
| `host_id` | شناسهی کاربر میزبان |
| `opponent_id` | شناسهی حریف |
| `is_host_turn` | یک `boolean` که بیانگر این است که الآن نوبت میزبان است یا خیر (مقدار پیشفرض آن `false` است)؛ مقدار آن در صورتی که بازی تمام شده باشد اهمیتی ندارد. |
| `status` | یک `enum` با مقدار پیشفرض `pending` به این معنا که بازی در انتظار تأیید است. اگر مقدار آن `rejected` باشد، یعنی حریف درخواست بازی را رد کرده است. اگر `ongoing` باشد، یعنی بازی در حال انجام است. اگر `win` باشد، یعنی میزبان برنده شده است. اگر `lose` باشد، یعنی حریف برنده شده است. اگر `tie` باشد، یعنی بازی مساوی شده است. |
3. مدل `Move`:
| نام فیلد | تعریف |
|--------------|------------------------------------------------------------------------------|
| `id` | شناسهی حرکت |
| `game_id` | شناسهی بازی |
| `position` | یک عدد در بازهی 1 تا 9 که بیانگر موقعیت حرکت است |
| `is_by_host` | یک `boolean` که بیانگر این است که این حرکت توسط میزبان صورت گرفته است یا خیر |
دو مورد زیر باید در مدلها پیادهسازی شوند:
1. متد `games` در مدل `User` را طوری پیادهسازی کنید که با اجرای متد `get` روی نتیجهی آن، بتوان آرایهای از بازیهایی که کاربر در آنها نقش میزبان یا حریف دارد را در اختیار داشت.
2. متد `getBoardAttribute` در مدل `Game` را طوری پیادهسازی کنید که صفحهی بازی را در قالب یک آرایهی ۳×۳ برگرداند. خانههای پرشده توسط شروعکنندهی بازی باید با `X` و خانههای پرشده توسط میزبان بازی باید با `O` نمایش داده شوند. خانههای خالی نیز باید با حرف `_` نمایش داده شوند. مثلاً اگر در یک بازی ابتدا حریف خانهی 9 را پر کند، سپس میزبان خانهی 5 را پر کند و در ادامه حریف خانهی 1 را پر کند، این متد باید چنین چیزی را برگرداند:
```php
[
['X', '_', '_'],
['_', 'O', '_'],
['_', '_', 'X']
]
```
## مسیرها
دو *route* زیر به کنترلر `API\AuthController` متصل هستند و باید پیادهسازی شوند:
1. **ثبتنام (`POST /register`):** سه فیلد `username`، `email` و `password` باید در بدنهی درخواست موجود باشند. همچنین، دو فیلد `username` و `email` باید در جدول `users` یکتا باشند. در صورتی که ورودی شرایط لازم را نداشتند، خطاهای پیشفرض *Validator* لاراول را در قالب *JSON* برگردانید. در غیر اینصورت، یک آرایه شامل کلید `token` با مقدار توکن تولیدشده توسط *Laravel Passport* را در پاسخ برگردانید.
2. **ورود (`POST /login`):** دو فیلد `username` و `password` باید در بدنهی درخواست موجود باشند. در صورتی که ورودی شرایط لازم را نداشتند، خطاهای پیشفرض *Validator* لاراول را در قالب *JSON* برگردانید. اگر نام کاربری یا رمز عبور نادرست بود، یک آرایه شامل کلید `error` با مقدار `invalid credentials` را در قالب *JSON* برگردانید. در غیر اینصورت، یک آرایه شامل کلید `token` با مقدار توکن تولیدشده توسط *Laravel Passport* را در پاسخ برگردانید.
پنج *route* زیر از *middleware* `auth:api` استفاده میکنند و نیازمند هدرهای `Accept: application/json` و `Authorization: Bearer token` هستند. این *route* ها به کنترلر `API\GameController` متصل هستند و باید پیادهسازی شوند:
3. **لیست بازیهای کاربر (`GET /games`):** بازیهایی که کاربر در آنها میزبان یا حریف دارد را در قالب *JSON* برگردانید.
4. **ساخت بازی جدید (`POST /games/create`):** فیلد `opponent` باید در بدنهی درخواست موجود باشد. در صورتی که ورودی شرایط لازم را نداشتند، خطاهای پیشفرض *Validator* لاراول را در قالب *JSON* برگردانید. در صورتی که کاربری با نام کاربری واردشده وجود نداشت، آرایهای شامل کلید `error` با مقدار `opponent does not exist` را با کد پاسخ *404* برگردانید. اگر نام کاربری واردشده با نام کاربری کاربر فعلی یکسان بود، آرایهای شامل کلید `error` با مقدار `you cannot play with yourself` را برگردانید. در غیر اینصورت، بازی را ایجاد کرده و آن را در پاسخ بهصورت *JSON* برگردانید.
5. **مشاهدهی اطلاعات بازی (`GET /games/{id}`):** در صورتی که بازیای با شناسهی واردشده وجود نداشت یا کاربر در آن میزبان یا حریف نبود، آرایهای شامل کلید `error` با مقدار `game does not exist` را با کد پاسخ *404* برگردانید. در غیر اینصورت، آبجکت بازی را در پاسخ برگردانید.
6. **تأیید درخواست بازی (`GET /games/accept/{id}`):** در صورتی که بازیای با شناسهی واردشده وجود نداشت یا کاربر در آن میزبان یا حریف نبود، آرایهای شامل کلید `error` با مقدار `game does not exist` را با کد پاسخ *404* برگردانید. اگر بازی در حال انجام بود، آرایهای شامل کلید `error` با مقدار `game is already ongoing` را برگردانید. اگر درخواست بازی قبلاً رد شده بود، آرایهای شامل کلید `error` با مقدار `game is already rejected` را برگردانید. اگر بازی خاتمه یافته بود، آرایهای شامل کلید `error` با مقدار `game is already finished` را برگردانید. در غیر اینصورت، وضعیت بازی را به `ongoing` تغییر داده و آرایهای شامل مقدار `success` را در قالب *JSON* برگردانید. اگر کاربر میزبان بازی بود، آرایهای شامل کلید `error` با مقدار `you cannot accept your games` را برگردانید.
7. **رد درخواست بازی (`GET /games/reject/{id}`):** در صورتی که بازیای با شناسهی واردشده وجود نداشت یا کاربر در آن میزبان یا حریف نبود، آرایهای شامل کلید `error` با مقدار `game does not exist` را با کد پاسخ *404* برگردانید. اگر بازی در حال انجام بود، آرایهای شامل کلید `error` با مقدار `game is already ongoing` را برگردانید. اگر درخواست بازی قبلاً رد شده بود، آرایهای شامل کلید `error` با مقدار `game is already rejected` را برگردانید. اگر بازی خاتمه یافته بود، آرایهای شامل کلید `error` با مقدار `game is already finished` را برگردانید. در غیر اینصورت، وضعیت بازی را به `rejected` تغییر داده و آرایهای شامل مقدار `success` را در قالب *JSON* برگردانید. اگر کاربر میزبان بازی بود، آرایهای شامل کلید `error` با مقدار `you cannot reject your games` را برگردانید.
8. **انجام حرکت در بازی (`POST /games/move/{id}`):** فیلد `position` باید با مقداری عددی و در بازهی 1 تا 9 در بدنهی درخواست موجود باشد. در صورتی که ورودی شرایط لازم را نداشتند، خطاهای پیشفرض *Validator* لاراول را در قالب *JSON* برگردانید. در صورتی که بازیای با شناسهی واردشده وجود نداشت یا کاربر در آن میزبان یا حریف نبود، آرایهای شامل کلید `error` با مقدار `game does not exist` را با کد پاسخ *404* برگردانید. اگر درخواست بازی در انتظار تأیید حریف بود، آرایهای شامل کلید `error` با مقدار `game is already pending` را برگردانید. اگر درخواست بازی قبلاً رد شده بود، آرایهای شامل کلید `error` با مقدار `game is already rejected` را برگردانید. اگر بازی خاتمه یافته بود، آرایهای شامل کلید `error` با مقدار `game is already finished` را برگردانید. اگر نوبت کاربر نبود، آرایهای شامل کلید `error` با مقدار `it is not your turn` را برگردانید (توجه کنید که همیشه حریف شروعکنندهی بازی است). اگر موقعیت حرکت در حال حاضر ثبت شده بود، آرایهای شامل کلید `error` با مقدار `cell is already full` را برگردانید. در غیر اینصورت، حرکت را ثبت کرده و آرایهای شامل مقدار `success` را در قالب *JSON* برگردانید. توجه کنید که پس از ثبت حرکت، باید بررسی کنید که آیا برندهی بازی (یا مساوی شدن بازی) میتواند تعیین شود یا خیر. در صورتی که این مورد قابل تشخیص بود، وضعیت بازی باید تغییر کند.
**توجه:** شما تنها مجاز به تغییر فایلهای موجود در پوشهی `/app` هستید.
# آنچه باید آپلود کنید
پس از اعمال تغییرات، کل پروژه به غیر از پوشهی `vendor` را *Zip* کرده و آپلود کنید. نام فایل *Zip* اهمیتی ندارد.
ارسال پاسخ برای این سؤال
در حال حاضر شما دسترسی ندارید.