در این مسئله، *سلیب* میخواهد بازیای تعریف کند تا از تنهایی ابدی خود نجات یابد و کمی سرگرم شود. این بار میخواهد تا یک برنامه تعاملی بنویسد تا عددی که کد دیگر او انتخاب کرده را پیدا کند :). بازی به این صورت است که *سلیب* میخواهد تابع `GuessMyNumber`را در فایل `main.go` پیادهسازی کند. امضای این تابع به شکل زیر است:
```go main.go go
func GuessMyNumber(game Game) string {
...
}
```
این تابع یک شی از نوع `Game` را دریافت میکند. هدف این تابع پیدا کردن عددی است که بازی انتخاب کرده و در داخل `Game` ذخیره شده است. اما پیدا کردن به شکل مستقیم امکان ندارد و باید حتما بازی کنیم! در واقع ما فقط به یک تابع دسترسی داریم که حدسهای خود را به آن میدهیم و حدسمان را بررسی میکند و به ما **راهنمایی** میکند که به جواب اصلی نزدیکتر شویم.
در نهایت تابع `GuessMyNumber` پس از اینکه حدسهای مختلفی میزند و نهایتا پاسخ را پیدا میکند، پاسخ خود را با یک پیام به شکل رشته باز میگرداند:
```
Your Number was <mark class="blue" title="عدد پیدا شده"><NUMBER></mark>
```
برای مثال اگر عدد بازی برابر با `269`باشد، این تابع پس از فهمیدن عدد باید عبارت زیر را برگرداند.
```
Your Number was 269
```
# جرئیات Game
در فایل `utils.go`، اینترفیس `Game` آمده است که در اینجا نیز میتوانیم آن را ببینیم:
```go utils.go go
package main
type Game interface {
CheckNumber(n int) string
}
```
تابع `CheckNumber` عددی که به عنوان حدس میگیرد را بررسی میکند و ۳ حالت مختلف را برمیگرداند:
+ اگر عدد ورودی تابع یا همان `n` از عدد مورد نظر بازی کمتر باشد، عبارت `My Number is Greater` را برمیگرداند.
+ اگر عدد ورودی تابع یا همان `n` از عدد مورد نظر بازی بیشتر باشد، عبارت `My Number is Lower` را برمیگرداند.
+ اگر عدد ورودی تابع یا همان `n` دقیقا برابر با عدد مورد نظر بازی باشد، عبارت `CORRECT` را برمیگرداند.
# نمرهدهی
از آنجا که اجرای تابع `CheckNumber` برای *سلیب* هزینه دارد (و شاید هم به دنبال پیچاندن و سختتر کردن مسئله است!) *سلیب* مسئله را در ۴ بخش تعریف کرده است:
| مسئله | نمره |
|---|---|
| با پرسیدن حداکثر ۳۶۰ پرسش به عدد برسد | ۲۵ |
| با پرسیدن حداکثر ۱۸۰ پرسش به عدد برسد | ۲۵ |
| با پرسیدن حداکثر ۶۰ پرسش به عدد برسد | ۲۵ |
| با پرسیدن حداکثر ۱۰ پرسش به عدد برسد | ۲۵ |
هرچه الگوریتم شما برای یافتن عدد بهتر باشد، نمره بیشتری در این مسئله دریافت میکنید :) پس به دنبال راهحلهای خلاقانه باشید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/54145/download_problem_initial_project/183357/) دانلود کنید. ساختار فایلهای پروژه بهصورت زیر است:
```
.
├── go.mod
├── go.sum
├── main.go
├── utils.go
└── main_sample_test.go
```
فایل `utils.go` شامل اینترفیس `Game` است. فایل `main_sample_test.go` نیز شامل یک پیادهسازی نمونه از `Game` است که برای تستهای اولیهی کد خود میتوانید از آن استفاده کنید. فایل `main.go` نیز شامل تابعی است که شما باید پیادهسازی کنید.
**توجه:** در این سؤال تنها مجاز به استفاده از کتابخانههای `bufio` و `fmt` و `math/rand` و `os` و `strings` هستید.
### محدودهی عددی که باید حدس بزنید بین ۱ تا ۳۶۰ است
# آنچه باید آپلود کنید
پس از پیادهسازی تابع `GuessMyNumber`، فایل `main.go` را آپلود کنید.
بعد از تقویمهای جلالی، میلادی و کلندرهای مجازی که گوگل و سایر شرکتها ارائه میدهند؛ ما در کوئرا به فکر طراحی تقویم خودمان افتادهایم. و وظیفهی آن به *اون یکی همکارمون* در شرکت محول شده.
از شما میخواهیم این پروژه را طبق معمول کوئرا برای *اون یکی همکارمون* پیادهسازی کنید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/54145/download_problem_initial_project/183358/) دانلود کنید. در این پروژه شما باید قسمتهای مشخصشده با `// TODO` را پیادهسازی کنید.
## جداول
ساختار `struct`هایی که جداول شما رو میسازند باید به شکل زیر باشد:
1- `User`
| نام ستون | نوع | ملاحظات |
|:------------------:|:------------------:|:-----------:|
| `gorm.Model` | `struct` |`PRIMARY KEY AUTO_INCREMENT`|
| `Username` |`string` | نام کاربری |
| `FirstName` | `string` | نام |
| `LastName` | `string` | نام خانوادگی |
2- `Calendar`
| نام ستون | نوع | ملاحظات |
|:------------------:|:------------------:|:-----------:|
| `gorm.Model` | `struct` |`PRIMARY KEY AUTO_INCREMENT`|
| `Name` |`string` | نام تقویم |
| `UserID` | `uint` | کلید خارجی به جدول `users` و ستون `id` |
3- `Appointment`
| نام ستون | نوع | ملاحظات |
|:------------------:|:------------------:|:-----------:|
| `gorm.Model` | `struct` |`PRIMARY KEY AUTO_INCREMENT`|
| `Subject` |`string` | موضوع ملاقات |
| `Description` | `string` | توضیحات ملاقات |
| `StartTime` | `time.Time` | زمان شروع ملاقات |
| `Length` | `uint` | مدت زمان ملاقات |
| `OwnerID` | `uint` | آیدی دارنده ملاقات |
| `OwnerType` | `string` | تایپ کلاس دارنده ملاقات |
4- `TaskList`
| نام ستون | نوع | ملاحظات |
|:------------------:|:------------------:|:-----------:|
| `gorm.Model` | `struct` |`PRIMARY KEY AUTO_INCREMENT`|
جهت اطلاعات بیشتر در مورد `gorm.Model` میتوانید به [این لینک](https://gorm.io/docs/models.html#gorm-Model) مراجعه نمایید.
## روابط
در ادامه شما باید به پیادهسازی روابط بین مدلها بپردازید که ارتباطات بین مدلها باید بهصورت زیر باشد:
+ هر کاربر یک تقویم دارد و هر تقویم متعلق به یک کاربر است.
+ هر کاربر میتواند چند وقتملاقات داشته باشد و هر وقتملاقات هم میتواند چند کاربر داشته باشد.(از طریق جدول `appointment_user`)
+ هر تقویم میتواند چند وقتملاقات داشته باشد.
+ هر فهرستوظیفه میتواند چند وقتملاقات داشته باشد.
+ هر وقتملاقات متعلق به یک تقویم یا فهرستوظیفه است.
## توابع
شما باید بدنه توابع زیر را به شکلی مطلوب تکمیل نمایید.
### تابع `RefreshDatabase`
این تابع با دریافت آبجکتی از نوع `*gorm.DB` و لیستی از جداول، ابتدا باید آنها را حذف کند و سپس آنها را طبق آخرین تغییرات بر اساس استراکتهای مربوطه بسازد. در صورت موفقیتآمیز بودن عملیات باید `Refresh database successfully done` به عنوان رشته برگردانده شود به همره `nil` و اگر خطایی در طی فرآیند رخ داد باید رشته خالی به همراه ارور مربوطه برگردانده شود.
امضای تابع به شکل زیر است:
```go
func RefreshDatabase(db *gorm.DB, tables []interface{}) (string, error){
// TODO: Implement
}
```
### تابع `SeedUser`
این تابع باید به صورت *Seeder* عمل کند و با دریافت پارامترهای لازم یک کاربر با تقویم و لیست ملاقاتهای مربوطه بسازد و در جدول ذخیره کند و در صورت موفقیتآمیز بودن عملیات پیام `Seeding database successfully done` را چاپ کند و در غیر این صورت ارور را به همراه یک رشته خالی برگرداند.
امضای تابع به شکل زیر است:
```go
func SeedUser(db *gorm.DB, username, firstName, lastName, calendarName, appointmentSubject string, startDate time.Time) (string, error) {
// TODO: Implement
}
```
### تابع `updateAppointment`
این تابع باید به تمام وقتملاقاتهایی که اسم تقویم آنها برابر با `calendarName`، زمان شروع آنها بین `startTime` و `endTime` و موضوع رویداد، شامل `keyword` باشد یک ساعت به زمان شروع ملاقاتها اضافه کند و به انتهای توضیحات آنها هم ` event` (یک فاصله و بعد *event*) را اضافه کند و در جدول ذخیره نماید در غیر این صورت ارور مربوطه را برگرداند.
امضای تابع به شکل زیر است:
```go
func updateAppointment(db *gorm.DB, calendarName string, startTime time.Time, endTime time.Time, keyword string) error {
// TODO: Implement
}
```
### اسکوپ `UserWithRangeAppointment`
این اسکوپ باید تمام کاربرانی که زمان شروع ملاقات آنها بین `startDate` و `endDate` است، موضوع ملاقات آنها برابر `subject` است و اسم تقویم آنها برابر `calendarTableName` را برگرداند.
امضای اسکوپ به شکل زیر است:
```go
func UserWithRangeAppointment(startDate, endDate time.Time, subject string, calendarTableName string) func(db *gorm.DB) *gorm.DB {
// TODO: Implement
}
```
# نکات
+ دقت کنید که قسمتهای مختلف سوال با هم مرتبط هستند و در صورت **صحیح نبودن جداول و روابط بین آنها** ممکن است **تستهای بعدی هم پاس نشوند**!
+ شما در این سوال باید از [GORM](https://gorm.io/) و امکانات آن استفاده کنید.
+ فایل `db.go` جهت اتصال *GORM* با دیتابیس است که در آن از دیزانپترن *singleton* استفاده شده و شما نیازی به تغییر آن ندارید و صرفا ثابتهای آن را جهت اجرا در محیط لوکال خود میتوانید تغییر دهید. فرض کنید سمت سرور نیز این فایل به درستی و مطابق دیتابیس سرور وجود دارد.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواسته شده، فایل `main.go` را که شامل تمام موارد گفته شده در بالا است آپلود کنید.
----------
در این سوال میخوایم یک پیام رسان ساده با استفاده از گولنگ پیاده سازی کنیم. فقط این پیام رسان برای آن دسته از افرادی است که خیلی حوصله فضای مجازی را ندارند و دوست ندارند هربار که وارد برنامه میشوند با تعداد زیادی از پیامهای جدید رو به رو شوند و به جایش دوست دارند هربار که خودشان خواستند سرور یکی از پیامهای جدیدشان را (در صورت وجود) برایشان نمایش دهد.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/54145/download_problem_initial_project/183356/) دانلود کنید. ساختار فایلهای پروژه به صورت زیر است:
```
.
├── go.mod
├── go.sum
├── message
│ └── message.go
├── server
│ └── server.go
├── test
│ └── sample_test.go
└── user
└── user.go
```
در هر کدام از فایلهای go توابع و استراکتهایی وجود دارد که باید کاملشان کنید که در ادامه به آنها خواهیم پرداخت.
## آنچه باید پیادهسازی کنید
در این پروژه هر کاربر میتواند به کاربر یا گروهی دیگر پیام ارسال کند. فقط برای ارسال پیام از سمت سرور به گیرنده چندتا شرط وجود دارد.
+ اول اینکه هر کاربر فقط باید یک پیام دریافت نشده داشته باشد. یعنی اگر چند کاربر به یک کاربر پیام فرستاده باشند، تا وقتی کاربر گیرنده آن یک پیام را نخواند بقیه پیامها به او ارسال نخواهند شد.
+ همچنین تا کاربر پیام را خواند نباید پیام بعدی برای او فرستاده شود. به این گونه که برای هر پیام یک زمانی در نظر گرفته میشود (مثلا n میلیثانیه) و سرور هر n میلیثانیه یک بار چک میکند که اگر گیرنده پیام خوانده نشده نداشته باشد، پیام جدید را به او ارسال میکند.
+ هر کاربر ممکن است `subscribed` باشد یا نباشد که با توجه به این ویژگی عدد n برای هر پیام از طرف او مشخص میشود. به این صورت که اگر `subscribed` باشد سرور هر 110 میلی ثانیه یک بار سعی میکند پیام او را به گیرنده بفرستد و اگر `subscribed` نباشد هر 290 میلی ثانیه یک بار. دقت کنید که این تایمر از زمان فرستادن هر پیام شروع میشود (یعنی هر پیام تایمر جدا دارد). همچنین هر بار 1 میلی ثانیه از مدت زمان تایمر کم میشود. یعنی اگر پیامی اولین بار پس از 110 میلی ثانیه سعی شود فرستاده شود و ناموفق بوده باشد (گیرنده پیام خوانده نشده از قبل داشته)، دفعه بعد پس از 109 میلی ثانیه دوباره سرور تلاش میکند ارسالش کند.
### فایل `message.go`
در این فایل استراکت حاوی اطلاعات پیامها وجود دارد که نباید آن را تغییر بدهید.
```go message.go go
type Message struct {
FromID int
ToID int
Text string
MsInQueueBeforeBeingSent time.Duration
}
```
فیلد اول نشان دهنده آیدی کاربر فرستنده است. فیلد دوم آیدی کاربر گیرنده. فیلد سوم متن پیام است. فیلد چهارم نشان دهنده زمانی است که طول کشیده است تا پیام به سمت گیرنده ارسال شود (به میلی ثانیه). درباره فیلد آخر دقت کنید که هربار سرور سعی میکند پیام را به گیرنده ارسال کند باید آپدیت شود (یعنی با زمان سیستم نباید کار کنید).
### فایل `server.go`
در این فایل استراکت زیر وجود دارد که باید خودتان کامل کنید.
```go server.go go
type Server struct {
}
```
همچنین تابع زیر را داریم که لیست کاربر و گروهها را میگیرد و سرور را برمیگرداند.
```go server.go go
func SetupServer(users []*user.User, groups []*user.Group) *Server {
}
```
در ادامه تابع زیر را داریم که باید لیستی از پیامهای موجود در صف سرور در آن لحظه را برگرداند. خروجی این تابع، ترتیبدار است. به این ترتیب پیامی که اول ارسال شده است باید اول لیست باشد.
```go server.go go
func (s *Server) GetQueuedMessages() []message.Message {
}
```
توجه: برای سرور امکان add و remove گروهها و کاربرها را نداریم. به این معنی که همان اطلاعاتی که موقع setup داشتیم، تا آخر برنامه معتبر است.
### فایل `user.go`
این فایل شامل دو دسته از استراکتها و توابع است که باید آنها را پیاده سازی کنید. قسمت اول مربوط به کاربرها است.
```go user.go go
type User struct {
}
func NewUser(id int, sub bool) *User {
}
func (u *User) SendMessage(receiver int, text string) error {
}
func (u *User) ReceiveMessage() message.Message {
}
```
تابع `NewUser` دو مقدار به عنوان ورودی میگیرد. اولی آیدی کاربر است. دومی یک بولین است که نشان میدهد این کاربر جز کاربرهای `subscribed` است یا خیر. در آخر یوزر ساخته شده را برمیگرداند.
تابع `SendMessage` نیز دو مقدار به عنوان ورودی میگیرد. اولی آیدی کاربر گیرنده و دومی متن پیام است. همچنین اگر آیدی گیرنده در سرور وجود نداشت، این تابع باید یک ارور برگرداند (متن ارور مهم نیست).
تابع `ReceiveMessage`: این تابع در صورت وجود پیام جدید برای کاربر، آن را برمیگرداند. در غیر این صورت یک استراکت خالی برمیگرداند
قسمت دوم مربوط به گروهها است (که کاربرها میتوانند عضو آنها شوند)
```go user.go go
type Group struct {
}
func NewGroup(id int, users []*User) *Group {
}
func (g *Group) AddUser(u *User) string {
}
func (g *Group) RemoveUser(u *User) string {
}
```
تابع `NewGroup`: آیدی گروه و لیست کاربرهای موجود در این گروه را میگیرد. تضمین میشود آیدی هیچ گروهی با هیچ کاربری یکسان نیست.
تابع `AddUser`: یک کاربر را میگیرد و به گروه اضافه میکند. اگر کاربر در گروه نبود و با موفقیت به گروه اضافه شد رشته بالا و اگر از قبل عضو گروه بود رشته پایین را برمیگرداند.
```
User <mark title="آیدی کاربر"><ID></mark> added to group <mark title="آیدی گروه"><ID></mark>
User <mark title="آیدی کاربر"><ID></mark> already in group <mark title="آیدی گروه"><ID></mark>
```
تابع `RemoveUser`: این تابع کاربر را از گروه حذف میکند. اگر کاربر در گروه بود و حذف شد پیام بالا و اگر اصلا عضو گروه نبود پیام پایین را برمیگرداند.
```
User <mark title="آیدی کاربر"><ID></mark> removed from group <mark title="آیدی گروه"><ID></mark>
User <mark title="آیدی کاربر"><ID></mark> isn't in group <mark title="آیدی گروه"><ID></mark>
```
درباره فرستادن پیام به گروهها:
+ اگر کاربر آیدی یک گروه را به عنوان گیرنده انتخاب کرده باشد، پیام باید به اعضای همه گروه فرستاده شود.
+ اگر کاربر فرستنده عضو گروه بود، پیام نباید به خودش فرستاده شود.
+ اعضای گروه هنگام ارسال پیام مهم هستند. یعنی اگر پیامی ارسال شد و سپس عضوی حذف شد پیام باید به او ارسال شود و اگر پیامی فرستاده شد و سپس کاربری به گروه اضافه شد آن پیام نباید برایش ارسال شود.
+ هر پیام که به گروه فرستاده میشود **فقط یک** بار به صف پیامها اضافه میشود، نه به تعداد کاربرها (یعنی فقط همان پیامی که `ToID` آن برابر آیدی گروه است) و هروقت به همه اعضای گروه ارسال شد از صف حذف میشود. همچنین هربار که این پیام به یکی از اعضای گروه ارسال میشود، فیلد `MsInQueueBeforeBeingSent ` این پیام در صف آپدیت میشود. به این معنی که اگر پیام برای بعضی از اعضا در 110 میلی ثانیه ارسال شد، این فیلد 110 میشود و... بدیهی است اگر پیام در یک زمان به همه اعضا ارسال شود از queue حذف میشود و دیگر به این پیام دسترسی نخواهیم داشت و نیازی به آپدیت این فیلد نیست.
## تضمینها
+ کاربری وسط برنامه از سرور حذف یا به سرور اضافه نمیشود.
+ آیدی کاربرها و گروهها هم پوشانی ندارد.
+ توابع پیاده سازی شده روی کاربرها و گروهها فقط روی کاربرها و گروههای موجود در سرور صدا زده میشود. فقط در هنگام `SendMessage` لازم است چک کنید آیدی گیرنده در سرور وجود دارد یا خیر.
# آنچه باید آپلود کنید
یک فایل زیپ آپلود کنید که وقتی آن را باز میکنیم دایرکتوریهای `user` که شامل `user.go` است و `server` که شامل `server.go` است را میبینیم.