شما قرار است بخشی از [بازی چشمک](https://fa.wikipedia.org/wiki/%DA%86%D8%B4%D9%85%DA%A9%E2%80%8C%D8%A8%D8%A7%D8%B2%DB%8C) را پیادهسازی کنید. در این نسخه از بازی قوانین زیر وجود دارد.
+ در ابتدای بازی، کارتها بین بازیکنها توزیع میشود. روی یکی از کارتها علامت (چشمک) قرار دارد.
+ بازیکنی که کارت چشمک دارد (چشمکزن)، در هر مرحله از بازی میتواند به یک یا چند بازیکن دیگر چشمک بزند.
+ با دریافت چشمک توسط یک بازیکن، آن بازیکن میمیرد (حذف میشود).
+ هر بازیکن میتواند حدس بزند، چه کسی چشمکزن است:
+ اگر حدس وی درست باشد، برنده شده و بازی تمام میشود؛
+ وگرنه، میمیرد (حذف میشود).
+ اگر در بازی، حداکثر دو نفر باقی بمانند، بازی تمام شده و چشمکزن پیروز میشود.
فایل [Source](https://quera.ir/contest/assignments/25979/download_problem_initial_project/87740/?noconvert=true) را دانلود کرده، وارد بسته `ir.mci.wink` شده و محتوای آن را ببینید:
## توضیحات:
+ واسط WinkGame: این واسط را باید **پیادهسازی کنید** (در کلاس WinkGameImpl).
+ واسط Dealer: یک متد `deal()` دارد که به هر یک از بازیکنها، یک کارت تخصیص میدهد که تنها یکی از آنها علامت چشمک دارد. این واسط **توسط ما** پیادهسازی میشود.
+ کلاس Card: نشاندهنده کارتی است که بین بازیکنها پخش میشود.
+ واسط PlayerState: واسطی شامل متدهایی برای دریافت وضعیت و انتقال وضعیت بازیکن است. این واسط یک متد `state()` دارد که وضعیت فعلی بازیکن را در قالب یک enum (از نوع PlayerState.State) بر میگرداند.
+ واسط Player: از واسط PlayerState ارثبری میکند. هر شی که به عنوان بازیکن در برنامه وجود دارد از نوع Player است. این واسط نیز **توسط ما** پیادهسازی میشود.
### نمودار وضعیت بازیکن:
شکل زیر، نمودار وضعیت بازیکن را نشان میدهد. با فراخوانی هر یک از متدها روی شیِ بازیکن، انتقال وضعیت انجام میشود. وضعیتها در واقع همان مقادیر PlayerState.State هستند که توسط متد `state()` از بازیکن قابل دریافت هستند.
![نمودار وضعیت](https://quera.ir/qbox/view/ASy6N8AxSY/WinkStateDiagram.png)
### مثال:
در بسته `ir.mci.wink.example`، یک پیادهسازی ساده از Player و Dealer وجود دارد که در متد `main` از کلاس Main استفاده شده است. با پیادهسازی صحیح WinkGameImpl و با اجرای برنامه، خروجی زیر به دست میآید:
```
Round #1:
[Player[id=100, state=PLAYING], Player[id=300, state=PLAYING], Player[id=500, state=PLAYING], Player[id=600, state=ELIMINATED]]
Round #2:
Winner: Player[id=500, state=WON]
[Player[id=100, state=FINISHED], Player[id=300, state=ELIMINATED], Player[id=500, state=WON], Player[id=600, state=ELIMINATED]]
```
باید توجه داشت که در تستها از پیادهسازی پیشرفتهترِ Player استفاده میشود. در این پیادهسازی در فراخوانی متدها، وضعیت فعلی بازیکن چک میشود و در صورتی که طبق نمودار وضعیت نبود، خطای `IllegalStateException` پرتاب میشود. این نکته در مورد پیادهسازی Dealer نیز صدق میکند.
### متدهای WinkGame:
+ متد `join(Player)`: بازیکن ورودی را به بازی ملحق میکند و متد `onJoin` این بازیکن را فراخوانی میکند.
+ متد `prepare()`: پس از فراخوانی این متد، دیگر بازیکنی نمیتواند به بازی ملحق شود. همچنین متد `onPrepare` برای تمامی بازیکنها فراخوانی میشود.
+ متد `dealing()`: در این متد، به کمک شیِ `dealer`، کارتها بین بازیکنها توزیع میشود و بازیکنِ چشمکزن را بر میگرداند. برای تحویل کارت به هر بازیکن، باید متد `onDeal(Card)` وی فراخوانی شود.
+ متد `playRound()`: یک مرحله از بازی را انجام میدهد و در صورتی که برنده بازی مشخص شود، وی را برمیگرداند:
+ ابتدا باید روی تمامیِ بازیکنهای در حال بازی (دارای وضعیت `Playing`)، متد `playRound(List<Player>)` فراخوانی شود. ورودیِ این متد باید بازیکنهای در حال بازی (به جز خود بازیکن) باشد.
+ با فراخوانی متد `detectedWinker()` از بازیکنها (به غیر از چشمکزن)، بازیکن چشمکزن را (در صورت تشخیص) برمیگرداند؛ اگر تشخیص بازیکنی درست بود: متد `onWin()` روی وی، متد `onDetected()` روی چشمکزن و متد `onFinished()` روی سایر بازیکنها فراخوانی میشود. بازیکن تشخیصدهنده به عنوان برنده برگردانده میشود. اگر تشخیص بازیکن اشتباه بود: متد `onWrongDetection()` روی وی فراخوانی میشود.
+ با فراخوانی متد `winkedPlayers()` از بازیکنِ چشمکزن، بازیکنهایی که چشمک خوردهاند برگردانده میشود و روی این بازیکنها، متد `onWink()` صدا زده میشود.
+ اگر حداکثر دو بازیکن باقیمانده باشند، چشمکزن برنده میشود و متد `onWin()` روی آن صدا زده میشود و به عنوان برنده برگردانده میشود. همچنین روی بازیکن باقیمانده نیز متد `onFinished()` صدا زده میشود.
### نکات و راهنمایی
+ در هر دور حداکثر یک نفر چشمکزن را درست تشخیص میدهد و یا به عبارت دیگر، بازی حداکثر یک برنده دارد.
+ متدهایی که خروجی `Optional` دارند هیچگاه نباید `null` برگردانند، بلکه باید یک `Optional` خالی برگردانند.
+ در فراخوانی متدها روی هر بازیکن دقت کنید و وضعیت بازیکن را در نظر بگیرید. برای مثال روی بازیکن حذفشده (وضعیت `Eliminated`) نمیتوان متد `onWink` را صدا زد.
# آنچه باید آپلود کنید:
یک فایل زیپ آپلود کنید که در آن بسته ir.mci.wink.impl به صورت زیر وجود داشته باشد:
```
.
└── ir
└── mci
└── wink
└── impl
└── WinkGameImpl.java
```
به صورتی که وقتی فایل زیپ را باز میکنیم، دقیقا شاخه ir را ببینیم که درون آن شاخه mci و درون آن شاخه wink و درون آن نیز شاخه impl قرار دارد. در داخل شاخه impl، **فقط و فقط** فایل WinkGameImpl.java وجود دارد.