From 58c059ab2469bf28bd9b2410b1793ff4ee040402 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Mon, 22 May 2023 22:44:04 +0800 Subject: [PATCH 001/117] IMDB Scraper init --- IMDB_Movie_Scraper/README.md | 13 +++++++++++++ IMDB_Movie_Scraper/scraper.py | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 IMDB_Movie_Scraper/README.md create mode 100644 IMDB_Movie_Scraper/scraper.py diff --git a/IMDB_Movie_Scraper/README.md b/IMDB_Movie_Scraper/README.md new file mode 100644 index 00000000..8bc2f8d0 --- /dev/null +++ b/IMDB_Movie_Scraper/README.md @@ -0,0 +1,13 @@ +# Which Movie Should I Watch using BeautifulSoup + +Date Started: 22/05/2023 + +Date Completed: - + +This is a mini-project to learn about web scraping using BeautifulSoup. Here is the [link](https://medium.com/@nishantsahoo/which-movie-should-i-watch-5c83a3c0f5b1) to the guide that I followed. + +Dependencies: +- lxml 4.9.2 +- beautifulsoup4 4.12.2 +- Python 3.9.13 +- urllib3 2.0.2 diff --git a/IMDB_Movie_Scraper/scraper.py b/IMDB_Movie_Scraper/scraper.py new file mode 100644 index 00000000..14173910 --- /dev/null +++ b/IMDB_Movie_Scraper/scraper.py @@ -0,0 +1,22 @@ +from bs4 import BeautifulSoup +import urllib3 + +# Getting user input +while True: + year = input("Enter a year to search for: ") + + if len(year) == 4 and year.isdigit(): + break + + print("Year must be 4 digits long.") + + +# Sending a GET request to specified URL and getting the response as a HTTPResponse object +url = "https://www.imdb.com/search/title/?release_date=" + year +http = urllib3.PoolManager() +response = http.request('GET', url).data + +# Parser +soup = BeautifulSoup(response, 'lxml') +# print(soup.prettify()) +print(soup.find_all('title')) \ No newline at end of file From 722adb7399ec077468253dff891993ca61fd827e Mon Sep 17 00:00:00 2001 From: thamruicong Date: Thu, 25 May 2023 00:29:28 +0800 Subject: [PATCH 002/117] used urllib default python lib instead of external urllib3 --- IMDB_Movie_Scraper/README.md | 1 - IMDB_Movie_Scraper/scraper.py | 18 ++++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/IMDB_Movie_Scraper/README.md b/IMDB_Movie_Scraper/README.md index 8bc2f8d0..198d39fc 100644 --- a/IMDB_Movie_Scraper/README.md +++ b/IMDB_Movie_Scraper/README.md @@ -10,4 +10,3 @@ Dependencies: - lxml 4.9.2 - beautifulsoup4 4.12.2 - Python 3.9.13 -- urllib3 2.0.2 diff --git a/IMDB_Movie_Scraper/scraper.py b/IMDB_Movie_Scraper/scraper.py index 14173910..38bb304d 100644 --- a/IMDB_Movie_Scraper/scraper.py +++ b/IMDB_Movie_Scraper/scraper.py @@ -1,10 +1,16 @@ from bs4 import BeautifulSoup -import urllib3 +from urllib.request import urlopen + +DEFAULT_YEAR = 2017 # Getting user input while True: year = input("Enter a year to search for: ") + if year == '': + year = str(DEFAULT_YEAR) + print("Setting default year to {}...".format(year)) + if len(year) == 4 and year.isdigit(): break @@ -13,10 +19,10 @@ # Sending a GET request to specified URL and getting the response as a HTTPResponse object url = "https://www.imdb.com/search/title/?release_date=" + year -http = urllib3.PoolManager() -response = http.request('GET', url).data +http_response = urlopen(url) +markup = http_response.read() # Parser -soup = BeautifulSoup(response, 'lxml') -# print(soup.prettify()) -print(soup.find_all('title')) \ No newline at end of file +soup = BeautifulSoup(markup, 'lxml') +print(soup.prettify()) +# print(soup.find_all('title')) \ No newline at end of file From e8a82d789986e6ea0b25f6e3c798adeb970a2c71 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Thu, 25 May 2023 00:36:36 +0800 Subject: [PATCH 003/117] forgot break statement --- IMDB_Movie_Scraper/scraper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IMDB_Movie_Scraper/scraper.py b/IMDB_Movie_Scraper/scraper.py index 38bb304d..b3abd557 100644 --- a/IMDB_Movie_Scraper/scraper.py +++ b/IMDB_Movie_Scraper/scraper.py @@ -10,6 +10,7 @@ if year == '': year = str(DEFAULT_YEAR) print("Setting default year to {}...".format(year)) + break if len(year) == 4 and year.isdigit(): break From 231f99e4c5a8a612c5dd997b90bca47640160981 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sun, 28 May 2023 12:47:30 +0800 Subject: [PATCH 004/117] scrap movie info --- IMDB_Movie_Scraper/scraper.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/IMDB_Movie_Scraper/scraper.py b/IMDB_Movie_Scraper/scraper.py index b3abd557..b1fcbe78 100644 --- a/IMDB_Movie_Scraper/scraper.py +++ b/IMDB_Movie_Scraper/scraper.py @@ -1,7 +1,9 @@ from bs4 import BeautifulSoup from urllib.request import urlopen +BASE_URL = 'https://www.imdb.com' DEFAULT_YEAR = 2017 +LIMIT = 30 # Getting user input while True: @@ -19,11 +21,28 @@ # Sending a GET request to specified URL and getting the response as a HTTPResponse object -url = "https://www.imdb.com/search/title/?release_date=" + year +url = BASE_URL + "/search/title/?release_date=" + year http_response = urlopen(url) markup = http_response.read() # Parser soup = BeautifulSoup(markup, 'lxml') -print(soup.prettify()) -# print(soup.find_all('title')) \ No newline at end of file +movie_list = soup.find_all('div', class_='lister-item mode-advanced') + +def get_movie_info(movie): + return { + 'title': movie.find('h3', class_='lister-item-header').a.text, + 'rating': movie.find('div', class_='inline-block ratings-imdb-rating').text.strip(), + 'path': movie.find('h3', class_='lister-item-header').a['href'] + } + +movie_list = list(map(get_movie_info, movie_list)) + +def print_movie_info(enumerator_item): + print(enumerator_item) + idx, movie = enumerator_item + + print(f'{idx}. {movie["title"]}, {movie["rating"]}\n{BASE_URL + movie["path"]}') + +print(list(enumerate(movie_list, 1))) +map(print_movie_info, list(enumerate(movie_list, 1))) \ No newline at end of file From eb6dbde554b139b7a503e34aa5a2267f6e5a2a60 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sun, 28 May 2023 14:26:42 +0800 Subject: [PATCH 005/117] print movies to console --- IMDB_Movie_Scraper/scraper.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/IMDB_Movie_Scraper/scraper.py b/IMDB_Movie_Scraper/scraper.py index b1fcbe78..42ca2f59 100644 --- a/IMDB_Movie_Scraper/scraper.py +++ b/IMDB_Movie_Scraper/scraper.py @@ -3,7 +3,6 @@ BASE_URL = 'https://www.imdb.com' DEFAULT_YEAR = 2017 -LIMIT = 30 # Getting user input while True: @@ -32,17 +31,11 @@ def get_movie_info(movie): return { 'title': movie.find('h3', class_='lister-item-header').a.text, - 'rating': movie.find('div', class_='inline-block ratings-imdb-rating').text.strip(), + 'rating': movie.find('div', class_='inline-block ratings-imdb-rating').text.strip() if movie.find('div', class_='inline-block ratings-imdb-rating') else 'No rating', 'path': movie.find('h3', class_='lister-item-header').a['href'] } movie_list = list(map(get_movie_info, movie_list)) -def print_movie_info(enumerator_item): - print(enumerator_item) - idx, movie = enumerator_item - - print(f'{idx}. {movie["title"]}, {movie["rating"]}\n{BASE_URL + movie["path"]}') - -print(list(enumerate(movie_list, 1))) -map(print_movie_info, list(enumerate(movie_list, 1))) \ No newline at end of file +for idx, movie in enumerate(movie_list, 1): + print(f'{idx}. {movie["title"]}, {movie["rating"]}\n{BASE_URL + movie["path"]}') \ No newline at end of file From ee50518a8d3c01f711bf4129cec6d0389c334433 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sun, 28 May 2023 14:27:05 +0800 Subject: [PATCH 006/117] complete IMDB Scraper --- IMDB_Movie_Scraper/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IMDB_Movie_Scraper/README.md b/IMDB_Movie_Scraper/README.md index 198d39fc..0b5ea809 100644 --- a/IMDB_Movie_Scraper/README.md +++ b/IMDB_Movie_Scraper/README.md @@ -2,7 +2,7 @@ Date Started: 22/05/2023 -Date Completed: - +Date Completed: 28/05/2023 This is a mini-project to learn about web scraping using BeautifulSoup. Here is the [link](https://medium.com/@nishantsahoo/which-movie-should-i-watch-5c83a3c0f5b1) to the guide that I followed. From 93d49971096a3f91cc5c9bff40bfa65b1cd5d7a2 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sun, 28 May 2023 14:28:25 +0800 Subject: [PATCH 007/117] add EOL to scraper.py --- IMDB_Movie_Scraper/scraper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IMDB_Movie_Scraper/scraper.py b/IMDB_Movie_Scraper/scraper.py index 42ca2f59..393e7f2b 100644 --- a/IMDB_Movie_Scraper/scraper.py +++ b/IMDB_Movie_Scraper/scraper.py @@ -38,4 +38,5 @@ def get_movie_info(movie): movie_list = list(map(get_movie_info, movie_list)) for idx, movie in enumerate(movie_list, 1): - print(f'{idx}. {movie["title"]}, {movie["rating"]}\n{BASE_URL + movie["path"]}') \ No newline at end of file + print(f'{idx}. {movie["title"]}, {movie["rating"]}\n{BASE_URL + movie["path"]}') + \ No newline at end of file From ec252417d3b5e125bc4dab9d1c14e470f2417328 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sun, 28 May 2023 14:42:53 +0800 Subject: [PATCH 008/117] update main README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f3119b7..1308e27b 100644 --- a/README.md +++ b/README.md @@ -371,7 +371,7 @@ To get started, simply fork this repo. Please refer to [CONTRIBUTING.md](CONTRIB - [Mining Twitter Data with Python](https://marcobonzanini.com/2015/03/02/mining-twitter-data-with-python-part-1/) - [Scrape a Website with Scrapy and MongoDB](https://realpython.com/blog/python/web-scraping-with-scrapy-and-mongodb/) - [How To Scrape With Python and Selenium WebDriver](http://www.byperth.com/2018/04/25/guide-web-scraping-101-what-you-need-to-know-and-how-to-scrape-with-python-selenium-webdriver/) -- [Which Movie Should I Watch using BeautifulSoup](https://medium.com/@nishantsahoo.in/which-movie-should-i-watch-5c83a3c0f5b1) +- [Which Movie Should I Watch using BeautifulSoup](https://medium.com/@nishantsahoo.in/which-movie-should-i-watch-5c83a3c0f5b1) :white_check_mark: ### Web Applications: From d9faa47a15cc956a2be8d10d950c27eacbb4bbe8 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sun, 28 May 2023 21:37:23 +0800 Subject: [PATCH 009/117] init README.md --- TelegramGPT/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 TelegramGPT/README.md diff --git a/TelegramGPT/README.md b/TelegramGPT/README.md new file mode 100644 index 00000000..cc70c667 --- /dev/null +++ b/TelegramGPT/README.md @@ -0,0 +1,10 @@ +# Telegram Bot with ChatGPT API + +Date Started: 28/05/2023 + +Date Completed: - + +This is a project that combines both the Telegram Chatbot API as well as OpenAI API. These are the two-part guide for the Telegram Bot - [Create a Telegram Bot](https://khashtamov.com/en/how-to-create-a-telegram-bot-using-python/), [Deploy a Telegram Bot](https://khashtamov.com/en/how-to-deploy-telegram-bot-django/). + +Dependencies: +- \ No newline at end of file From 885bd5beca6898624bd1e02eaafdad04a42ffcec Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sun, 28 May 2023 21:45:50 +0800 Subject: [PATCH 010/117] add references to README.md --- TelegramGPT/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/TelegramGPT/README.md b/TelegramGPT/README.md index cc70c667..abd40322 100644 --- a/TelegramGPT/README.md +++ b/TelegramGPT/README.md @@ -7,4 +7,8 @@ Date Completed: - This is a project that combines both the Telegram Chatbot API as well as OpenAI API. These are the two-part guide for the Telegram Bot - [Create a Telegram Bot](https://khashtamov.com/en/how-to-create-a-telegram-bot-using-python/), [Deploy a Telegram Bot](https://khashtamov.com/en/how-to-deploy-telegram-bot-django/). Dependencies: -- \ No newline at end of file +- + +References: +- [Telegram Bot Features](https://core.telegram.org/bots/features#what-features-do-bots-have) +- [Telegram Bot API](https://core.telegram.org/bots/api#authorizing-your-bot) \ No newline at end of file From 751e907deff827f16ad789987e289baece908c74 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sun, 28 May 2023 21:48:04 +0800 Subject: [PATCH 011/117] Add BS4 reference to README.md --- IMDB_Movie_Scraper/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/IMDB_Movie_Scraper/README.md b/IMDB_Movie_Scraper/README.md index 0b5ea809..9a51d90a 100644 --- a/IMDB_Movie_Scraper/README.md +++ b/IMDB_Movie_Scraper/README.md @@ -10,3 +10,6 @@ Dependencies: - lxml 4.9.2 - beautifulsoup4 4.12.2 - Python 3.9.13 + +References: +- [BeautifulSoup4 API](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) \ No newline at end of file From 6776f3ea3dd09a23b82a885aa2bd02c961ec912a Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sun, 28 May 2023 22:44:40 +0800 Subject: [PATCH 012/117] add .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..95042264 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/TelegramGPT/API_KEY \ No newline at end of file From 7bee30a931ff5bfaaca43cf6695e44210fe5d618 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sun, 28 May 2023 22:51:37 +0800 Subject: [PATCH 013/117] update references --- TelegramGPT/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TelegramGPT/README.md b/TelegramGPT/README.md index abd40322..62cc996f 100644 --- a/TelegramGPT/README.md +++ b/TelegramGPT/README.md @@ -10,5 +10,5 @@ Dependencies: - References: -- [Telegram Bot Features](https://core.telegram.org/bots/features#what-features-do-bots-have) -- [Telegram Bot API](https://core.telegram.org/bots/api#authorizing-your-bot) \ No newline at end of file +- [Telegram Bot Features](https://core.telegram.org/bots/features) +- [Telegram Bot API](https://core.telegram.org/bots/api) \ No newline at end of file From ea5f4be65eb4918cb1ee9056a1956cac815989d2 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Wed, 31 May 2023 21:29:58 +0800 Subject: [PATCH 014/117] update README --- TelegramGPT/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/TelegramGPT/README.md b/TelegramGPT/README.md index 62cc996f..6f1dd333 100644 --- a/TelegramGPT/README.md +++ b/TelegramGPT/README.md @@ -4,11 +4,14 @@ Date Started: 28/05/2023 Date Completed: - -This is a project that combines both the Telegram Chatbot API as well as OpenAI API. These are the two-part guide for the Telegram Bot - [Create a Telegram Bot](https://khashtamov.com/en/how-to-create-a-telegram-bot-using-python/), [Deploy a Telegram Bot](https://khashtamov.com/en/how-to-deploy-telegram-bot-django/). +This is a project that combines both the Telegram Chatbot API as well as OpenAI API. ~~These are the two-part guide for the Telegram Bot - [Create a Telegram Bot](https://khashtamov.com/en/how-to-create-a-telegram-bot-using-python/), [Deploy a Telegram Bot](https://khashtamov.com/en/how-to-deploy-telegram-bot-django/).~~ Dependencies: - References: -- [Telegram Bot Features](https://core.telegram.org/bots/features) -- [Telegram Bot API](https://core.telegram.org/bots/api) \ No newline at end of file +- ~~[Telegram Bot Features](https://core.telegram.org/bots/features)~~ +- ~~[Telegram Bot API](https://core.telegram.org/bots/api)~~ +- [python-telegram-bot](https://python-telegram-bot.org/) (my Bot API library of choice) +- [Introduction to API](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Introduction-to-the-API) +- [Your First Bot](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Extensions---Your-first-Bot) \ No newline at end of file From 8f69db6039746ee0a79425e94d5df228af449c95 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Wed, 31 May 2023 21:58:15 +0800 Subject: [PATCH 015/117] sending messages --- TelegramGPT/main.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 TelegramGPT/main.py diff --git a/TelegramGPT/main.py b/TelegramGPT/main.py new file mode 100644 index 00000000..839cdf26 --- /dev/null +++ b/TelegramGPT/main.py @@ -0,0 +1,16 @@ +import asyncio +import telegram + +def getAPIKey(): + with open('API_KEY', 'r') as f: + k = f.read() + + return k + +async def main(): + bot = telegram.Bot(getAPIKey()) + async with bot: + await bot.sendMessage(text='Hello World!', chat_id=1239260027) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file From 2d1417129d4d616dfe141f798820a80c669a10e9 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Fri, 2 Jun 2023 00:13:14 +0800 Subject: [PATCH 016/117] add /start command handler --- TelegramGPT/main.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/TelegramGPT/main.py b/TelegramGPT/main.py index 839cdf26..414d9427 100644 --- a/TelegramGPT/main.py +++ b/TelegramGPT/main.py @@ -1,5 +1,11 @@ -import asyncio -import telegram +import logging +from telegram import Update +from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes + +logging.basicConfig( + format='%(asctime)s : %(name)s : %(levelname)s : %(message)s', + level=logging.INFO +) def getAPIKey(): with open('API_KEY', 'r') as f: @@ -7,10 +13,13 @@ def getAPIKey(): return k -async def main(): - bot = telegram.Bot(getAPIKey()) - async with bot: - await bot.sendMessage(text='Hello World!', chat_id=1239260027) +async def _start(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text='Hello World!') if __name__ == '__main__': - asyncio.run(main()) \ No newline at end of file + application = ApplicationBuilder().token(getAPIKey()).build() + + startHandler = CommandHandler('start', _start) + application.add_handler(startHandler) + + application.run_polling() \ No newline at end of file From fa0923b34c5137fcff3dac71d9859f528b84fa66 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Fri, 2 Jun 2023 00:21:49 +0800 Subject: [PATCH 017/117] echo handler --- TelegramGPT/main.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/TelegramGPT/main.py b/TelegramGPT/main.py index 414d9427..6b029461 100644 --- a/TelegramGPT/main.py +++ b/TelegramGPT/main.py @@ -1,6 +1,6 @@ import logging from telegram import Update -from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes +from telegram.ext import filters, ApplicationBuilder, CommandHandler, MessageHandler, ContextTypes logging.basicConfig( format='%(asctime)s : %(name)s : %(levelname)s : %(message)s', @@ -16,10 +16,15 @@ def getAPIKey(): async def _start(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text='Hello World!') +async def _echo(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=update.message.text) + if __name__ == '__main__': application = ApplicationBuilder().token(getAPIKey()).build() - startHandler = CommandHandler('start', _start) - application.add_handler(startHandler) + start_handler = CommandHandler('start', _start) + echo_handler = MessageHandler(filters.TEXT & ~filters.COMMAND, _echo) + + application.add_handlers([start_handler, echo_handler]) application.run_polling() \ No newline at end of file From 5629e8cda90bcc6bd4359794d81b3a4df26f7d78 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Fri, 2 Jun 2023 00:30:10 +0800 Subject: [PATCH 018/117] add dependencies --- TelegramGPT/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TelegramGPT/README.md b/TelegramGPT/README.md index 6f1dd333..76395045 100644 --- a/TelegramGPT/README.md +++ b/TelegramGPT/README.md @@ -7,7 +7,7 @@ Date Completed: - This is a project that combines both the Telegram Chatbot API as well as OpenAI API. ~~These are the two-part guide for the Telegram Bot - [Create a Telegram Bot](https://khashtamov.com/en/how-to-create-a-telegram-bot-using-python/), [Deploy a Telegram Bot](https://khashtamov.com/en/how-to-deploy-telegram-bot-django/).~~ Dependencies: -- +- python-telegram-bot 20.3 References: - ~~[Telegram Bot Features](https://core.telegram.org/bots/features)~~ From 339469c05853a6f003f8afaceae1867bc5559556 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Wed, 7 Jun 2023 23:13:18 +0800 Subject: [PATCH 019/117] refactor .py files --- TelegramGPT/handlers.py | 14 ++++++++++++++ TelegramGPT/main.py | 24 +++++------------------- TelegramGPT/utils.py | 5 +++++ 3 files changed, 24 insertions(+), 19 deletions(-) create mode 100644 TelegramGPT/handlers.py create mode 100644 TelegramGPT/utils.py diff --git a/TelegramGPT/handlers.py b/TelegramGPT/handlers.py new file mode 100644 index 00000000..51fed0fb --- /dev/null +++ b/TelegramGPT/handlers.py @@ -0,0 +1,14 @@ +from telegram import Update +from telegram.ext import CommandHandler, MessageHandler, filters, ContextTypes + +async def _start(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text='Hello World!') + +async def _echo(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=update.message.text) + +def getHandlers(): + start_handler = CommandHandler('start', _start) + echo_handler = MessageHandler(filters.TEXT & ~filters.COMMAND, _echo) + + return [start_handler, echo_handler] \ No newline at end of file diff --git a/TelegramGPT/main.py b/TelegramGPT/main.py index 6b029461..7ac0014b 100644 --- a/TelegramGPT/main.py +++ b/TelegramGPT/main.py @@ -1,30 +1,16 @@ import logging -from telegram import Update -from telegram.ext import filters, ApplicationBuilder, CommandHandler, MessageHandler, ContextTypes +from telegram.ext import ApplicationBuilder + +from handlers import getHandlers +from utils import getAPIKey logging.basicConfig( format='%(asctime)s : %(name)s : %(levelname)s : %(message)s', level=logging.INFO ) -def getAPIKey(): - with open('API_KEY', 'r') as f: - k = f.read() - - return k - -async def _start(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text='Hello World!') - -async def _echo(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=update.message.text) - if __name__ == '__main__': application = ApplicationBuilder().token(getAPIKey()).build() - - start_handler = CommandHandler('start', _start) - echo_handler = MessageHandler(filters.TEXT & ~filters.COMMAND, _echo) - - application.add_handlers([start_handler, echo_handler]) + application.add_handlers(getHandlers()) application.run_polling() \ No newline at end of file diff --git a/TelegramGPT/utils.py b/TelegramGPT/utils.py new file mode 100644 index 00000000..9963c5eb --- /dev/null +++ b/TelegramGPT/utils.py @@ -0,0 +1,5 @@ +def getAPIKey(): + with open('API_KEY', 'r') as f: + k = f.read() + + return k \ No newline at end of file From 91cc5e7f27a88f45cdcedf2b7c7e301b30751d02 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Wed, 7 Jun 2023 23:14:53 +0800 Subject: [PATCH 020/117] update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 95042264..a5b6fe02 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ +__pycache__/ + /TelegramGPT/API_KEY \ No newline at end of file From a80ee668efaf30127cf7f2bc533a52bcbf6deb44 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Thu, 8 Jun 2023 00:11:08 +0800 Subject: [PATCH 021/117] google 2.0 handler --- TelegramGPT/handlers.py | 44 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/TelegramGPT/handlers.py b/TelegramGPT/handlers.py index 51fed0fb..9b98ff9e 100644 --- a/TelegramGPT/handlers.py +++ b/TelegramGPT/handlers.py @@ -1,5 +1,8 @@ from telegram import Update -from telegram.ext import CommandHandler, MessageHandler, filters, ContextTypes +from telegram.ext import CommandHandler, MessageHandler, filters, ContextTypes, ConversationHandler +import logging + +log = logging.getLogger(__name__) async def _start(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text='Hello World!') @@ -7,8 +10,45 @@ async def _start(update: Update, context: ContextTypes.DEFAULT_TYPE): async def _echo(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=update.message.text) +async def _invalid_command(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text='Invalid command!') + +async def _invalid_input(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text='Invalid input!') + +GOOGLE = 0 +async def _start_google(update: Update, context: ContextTypes.DEFAULT_TYPE): + text = 'Welcome to Google 2.0!\nThis is just a simple ChatGPT interface.\n\nType /cancel to cancel.' + await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + + return GOOGLE + +async def _google(update: Update, context: ContextTypes.DEFAULT_TYPE): + text = 'Google' + await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + + return GOOGLE + +async def _cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): + log.info('User %s stopped Google 2.0', update.effective_user.username) + text = 'Google 2.0 Shutting down...' + await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + + return ConversationHandler.END + def getHandlers(): start_handler = CommandHandler('start', _start) echo_handler = MessageHandler(filters.TEXT & ~filters.COMMAND, _echo) - return [start_handler, echo_handler] \ No newline at end of file + # Does not work correctly, need to disable all other handlers while in google_handler mode + google_handler = ConversationHandler( + entry_points=[CommandHandler('google', _start_google)], + states={ + GOOGLE: [MessageHandler(filters.TEXT, _google)] + }, + fallbacks=[CommandHandler('cancel', _cancel), MessageHandler(filters.ALL, _invalid_input)] + ) + + invalid_command_handler = MessageHandler(filters.COMMAND, _invalid_command) + + return [start_handler, echo_handler, invalid_command_handler] \ No newline at end of file From 5d513b2e5928a8c53287f7faa125984c3e90f3d2 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Thu, 8 Jun 2023 21:21:44 +0800 Subject: [PATCH 022/117] refactor code layout, automate state values in a single file --- TelegramGPT/handlers.py | 69 +++++++++++++++++++++++------------------ TelegramGPT/utils.py | 20 +++++++++++- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/TelegramGPT/handlers.py b/TelegramGPT/handlers.py index 9b98ff9e..aefe3fcf 100644 --- a/TelegramGPT/handlers.py +++ b/TelegramGPT/handlers.py @@ -1,54 +1,63 @@ from telegram import Update from telegram.ext import CommandHandler, MessageHandler, filters, ContextTypes, ConversationHandler -import logging +import utils -log = logging.getLogger(__name__) - -async def _start(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text='Hello World!') - -async def _echo(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=update.message.text) +async def _help(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.HELP_TEXT) async def _invalid_command(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text='Invalid command!') + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.INVALID_COMMAND_TEXT) async def _invalid_input(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text='Invalid input!') + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.INVALID_INPUT_TEXT) + +async def _cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CANCELLED_TEXT) + + return ConversationHandler.END -GOOGLE = 0 async def _start_google(update: Update, context: ContextTypes.DEFAULT_TYPE): - text = 'Welcome to Google 2.0!\nThis is just a simple ChatGPT interface.\n\nType /cancel to cancel.' - await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_GOOGLE_TEXT) + + return utils.GOOGLE + +async def _start_chat(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_CHAT_TEXT) - return GOOGLE + return utils.CHAT + +async def _start_game(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_GAME_TEXT) + + return utils.GAME async def _google(update: Update, context: ContextTypes.DEFAULT_TYPE): text = 'Google' await context.bot.send_message(chat_id=update.effective_chat.id, text=text) - return GOOGLE - -async def _cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): - log.info('User %s stopped Google 2.0', update.effective_user.username) - text = 'Google 2.0 Shutting down...' +async def _chat(update: Update, context: ContextTypes.DEFAULT_TYPE): + text = 'Chat' + await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + +async def _game(update: Update, context: ContextTypes.DEFAULT_TYPE): + text = 'Game' await context.bot.send_message(chat_id=update.effective_chat.id, text=text) - - return ConversationHandler.END def getHandlers(): - start_handler = CommandHandler('start', _start) - echo_handler = MessageHandler(filters.TEXT & ~filters.COMMAND, _echo) - - # Does not work correctly, need to disable all other handlers while in google_handler mode - google_handler = ConversationHandler( - entry_points=[CommandHandler('google', _start_google)], + utils.init_states() + entry_points = [CommandHandler('google', _start_google), CommandHandler('chat', _start_chat), CommandHandler('game', _start_game)] + main_handler = ConversationHandler( + entry_points=entry_points, states={ - GOOGLE: [MessageHandler(filters.TEXT, _google)] + utils.GOOGLE: [MessageHandler(filters.TEXT & ~filters.COMMAND, _google)], + utils.CHAT: [MessageHandler(filters.TEXT & ~filters.COMMAND, _chat)], + utils.GAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, _game)], }, - fallbacks=[CommandHandler('cancel', _cancel), MessageHandler(filters.ALL, _invalid_input)] + fallbacks=[CommandHandler('cancel', _cancel)] + entry_points ) + help_command_handler = CommandHandler('help', _help) invalid_command_handler = MessageHandler(filters.COMMAND, _invalid_command) + invalid_input_handler = MessageHandler(filters.ALL, _invalid_input) - return [start_handler, echo_handler, invalid_command_handler] \ No newline at end of file + return [main_handler, help_command_handler, invalid_command_handler, invalid_input_handler] \ No newline at end of file diff --git a/TelegramGPT/utils.py b/TelegramGPT/utils.py index 9963c5eb..464abcf4 100644 --- a/TelegramGPT/utils.py +++ b/TelegramGPT/utils.py @@ -1,5 +1,23 @@ +from pathlib import Path + +HELP_TEXT = 'Help' +INVALID_COMMAND_TEXT = 'Invalid command!' +INVALID_INPUT_TEXT = 'Invalid input!' +CANCELLED_TEXT = 'Cancelled!' +START_GOOGLE_TEXT = 'Welcome to Google 2.0!\nThis is just a simple ChatGPT interface.\n\nType /cancel to cancel.' +START_CHAT_TEXT ='Welcome chat' +START_GAME_TEXT = 'Welcome game' + +def init_states(): + states = ['GOOGLE', 'CHAT', 'GAME'] + + for val, state in enumerate(states): + globals()[state] = val + def getAPIKey(): - with open('API_KEY', 'r') as f: + path = Path(__file__).parent / 'API_KEY' + + with path.open() as f: k = f.read() return k \ No newline at end of file From a9b78034892e19ce78cd798cbc19c861f38e1b06 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sat, 10 Jun 2023 17:52:27 +0800 Subject: [PATCH 023/117] openai working test --- TelegramGPT/.env | 2 ++ TelegramGPT/README.md | 1 + TelegramGPT/generate.py | 8 ++++++++ TelegramGPT/main.py | 4 ++-- TelegramGPT/utils.py | 15 ++++++++------- 5 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 TelegramGPT/.env create mode 100644 TelegramGPT/generate.py diff --git a/TelegramGPT/.env b/TelegramGPT/.env new file mode 100644 index 00000000..84b887c5 --- /dev/null +++ b/TelegramGPT/.env @@ -0,0 +1,2 @@ +TELEGRAM_TOKEN=6037887376:AAH38fxJRc13NzgG7uRGLfOMKbaxz-QvlG0 +OPENAI_API_KEY=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 \ No newline at end of file diff --git a/TelegramGPT/README.md b/TelegramGPT/README.md index 76395045..1fb01f3f 100644 --- a/TelegramGPT/README.md +++ b/TelegramGPT/README.md @@ -8,6 +8,7 @@ This is a project that combines both the Telegram Chatbot API as well as OpenAI Dependencies: - python-telegram-bot 20.3 +- openai 0.27.8 References: - ~~[Telegram Bot Features](https://core.telegram.org/bots/features)~~ diff --git a/TelegramGPT/generate.py b/TelegramGPT/generate.py new file mode 100644 index 00000000..2bd8b380 --- /dev/null +++ b/TelegramGPT/generate.py @@ -0,0 +1,8 @@ +import openai +from utils import getOpenAIAPIKey + +openai.api_key = getOpenAIAPIKey() + +# response = openai.Completion.create(model='text-davinci-003', prompt='This is a test') + +# print(response.choices[0].text.strip()) \ No newline at end of file diff --git a/TelegramGPT/main.py b/TelegramGPT/main.py index 7ac0014b..f84cc096 100644 --- a/TelegramGPT/main.py +++ b/TelegramGPT/main.py @@ -2,7 +2,7 @@ from telegram.ext import ApplicationBuilder from handlers import getHandlers -from utils import getAPIKey +from utils import getTelegramToken logging.basicConfig( format='%(asctime)s : %(name)s : %(levelname)s : %(message)s', @@ -10,7 +10,7 @@ ) if __name__ == '__main__': - application = ApplicationBuilder().token(getAPIKey()).build() + application = ApplicationBuilder().token(getTelegramToken()).build() application.add_handlers(getHandlers()) application.run_polling() \ No newline at end of file diff --git a/TelegramGPT/utils.py b/TelegramGPT/utils.py index 464abcf4..af9a393c 100644 --- a/TelegramGPT/utils.py +++ b/TelegramGPT/utils.py @@ -1,4 +1,7 @@ -from pathlib import Path +import os +from dotenv import load_dotenv + +load_dotenv() HELP_TEXT = 'Help' INVALID_COMMAND_TEXT = 'Invalid command!' @@ -14,10 +17,8 @@ def init_states(): for val, state in enumerate(states): globals()[state] = val -def getAPIKey(): - path = Path(__file__).parent / 'API_KEY' +def getTelegramToken(): + return os.getenv('TELEGRAM_TOKEN') - with path.open() as f: - k = f.read() - - return k \ No newline at end of file +def getOpenAIAPIKey(): + return os.getenv('OPENAI_API_KEY') \ No newline at end of file From 50cf886b0adc8c7d327e460e092b1426f389401f Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sat, 10 Jun 2023 21:26:28 +0800 Subject: [PATCH 024/117] remove .env --- .gitignore | 2 +- TelegramGPT/.env | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 TelegramGPT/.env diff --git a/.gitignore b/.gitignore index a5b6fe02..3c32e91a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ __pycache__/ -/TelegramGPT/API_KEY \ No newline at end of file +.env \ No newline at end of file diff --git a/TelegramGPT/.env b/TelegramGPT/.env deleted file mode 100644 index 84b887c5..00000000 --- a/TelegramGPT/.env +++ /dev/null @@ -1,2 +0,0 @@ -TELEGRAM_TOKEN=6037887376:AAH38fxJRc13NzgG7uRGLfOMKbaxz-QvlG0 -OPENAI_API_KEY=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 \ No newline at end of file From b537a2d2c7cf617d1108f87993f45ce5e878660a Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sat, 10 Jun 2023 22:55:00 +0800 Subject: [PATCH 025/117] google done --- TelegramGPT/generate.py | 16 ++++++++++++--- TelegramGPT/handlers.py | 43 ++++++++++++++++++++++++++++++----------- TelegramGPT/utils.py | 5 +++-- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/TelegramGPT/generate.py b/TelegramGPT/generate.py index 2bd8b380..dafb58f9 100644 --- a/TelegramGPT/generate.py +++ b/TelegramGPT/generate.py @@ -1,8 +1,18 @@ import openai from utils import getOpenAIAPIKey +import logging -openai.api_key = getOpenAIAPIKey() +log = logging.getLogger(__name__) -# response = openai.Completion.create(model='text-davinci-003', prompt='This is a test') +openai.api_key = getOpenAIAPIKey() +MAX_TOKENS = 50 -# print(response.choices[0].text.strip()) \ No newline at end of file +def query(prompt, model='text-davinci-003'): + response = openai.Completion.create( + model=model, + prompt=prompt, + max_tokens=MAX_TOKENS, + temperature=0.3, + ) + log.info(response) + return response.choices[0].text.strip() \ No newline at end of file diff --git a/TelegramGPT/handlers.py b/TelegramGPT/handlers.py index aefe3fcf..0aac5ba3 100644 --- a/TelegramGPT/handlers.py +++ b/TelegramGPT/handlers.py @@ -1,44 +1,64 @@ from telegram import Update from telegram.ext import CommandHandler, MessageHandler, filters, ContextTypes, ConversationHandler import utils +from generate import query +############################ UTILS ############################ async def _help(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.HELP_TEXT) async def _invalid_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.INVALID_COMMAND_TEXT) + await _help(update, context) async def _invalid_input(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.INVALID_INPUT_TEXT) + await _help(update, context) async def _cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CANCELLED_TEXT) return ConversationHandler.END +async def _ignore(update: Update, context: ContextTypes.DEFAULT_TYPE): + pass + +############################ GOOGLE ############################ async def _start_google(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_GOOGLE_TEXT) return utils.GOOGLE +async def _google_help(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.GOOGLE_HELP_TEXT) + await _help(update, context) + +async def _google(update: Update, context: ContextTypes.DEFAULT_TYPE): + reply = query(update.effective_message.text) + await context.bot.send_message(chat_id=update.effective_chat.id, text=reply) + +############################ CHAT ############################ async def _start_chat(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_CHAT_TEXT) return utils.CHAT +async def _chat_help(update: Update, context: ContextTypes.DEFAULT_TYPE): + pass + +async def _chat(update: Update, context: ContextTypes.DEFAULT_TYPE): + text = 'Chat' + await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + +############################ GAME ############################ async def _start_game(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_GAME_TEXT) return utils.GAME -async def _google(update: Update, context: ContextTypes.DEFAULT_TYPE): - text = 'Google' - await context.bot.send_message(chat_id=update.effective_chat.id, text=text) +async def _game_help(update: Update, context: ContextTypes.DEFAULT_TYPE): + pass -async def _chat(update: Update, context: ContextTypes.DEFAULT_TYPE): - text = 'Chat' - await context.bot.send_message(chat_id=update.effective_chat.id, text=text) - async def _game(update: Update, context: ContextTypes.DEFAULT_TYPE): text = 'Game' await context.bot.send_message(chat_id=update.effective_chat.id, text=text) @@ -49,15 +69,16 @@ def getHandlers(): main_handler = ConversationHandler( entry_points=entry_points, states={ - utils.GOOGLE: [MessageHandler(filters.TEXT & ~filters.COMMAND, _google)], - utils.CHAT: [MessageHandler(filters.TEXT & ~filters.COMMAND, _chat)], - utils.GAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, _game)], + utils.GOOGLE: [MessageHandler(filters.TEXT & ~filters.COMMAND, _google), CommandHandler('help', _google_help)], + utils.CHAT: [MessageHandler(filters.TEXT & ~filters.COMMAND, _chat), CommandHandler('help', _chat_help)], + utils.GAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, _game), CommandHandler('help', _game_help)], }, fallbacks=[CommandHandler('cancel', _cancel)] + entry_points ) + ignore_handler = MessageHandler(filters.UpdateType.EDITED, _ignore) help_command_handler = CommandHandler('help', _help) invalid_command_handler = MessageHandler(filters.COMMAND, _invalid_command) invalid_input_handler = MessageHandler(filters.ALL, _invalid_input) - return [main_handler, help_command_handler, invalid_command_handler, invalid_input_handler] \ No newline at end of file + return [ignore_handler, main_handler, help_command_handler, invalid_command_handler, invalid_input_handler] \ No newline at end of file diff --git a/TelegramGPT/utils.py b/TelegramGPT/utils.py index af9a393c..5d3661c4 100644 --- a/TelegramGPT/utils.py +++ b/TelegramGPT/utils.py @@ -3,13 +3,14 @@ load_dotenv() -HELP_TEXT = 'Help' +HELP_TEXT = 'Valid commands:\n/help - Show this help message\n/google - Ask me a question\n/chat - Chat with me\n/game - Play a game with me' INVALID_COMMAND_TEXT = 'Invalid command!' INVALID_INPUT_TEXT = 'Invalid input!' CANCELLED_TEXT = 'Cancelled!' -START_GOOGLE_TEXT = 'Welcome to Google 2.0!\nThis is just a simple ChatGPT interface.\n\nType /cancel to cancel.' +START_GOOGLE_TEXT = 'Welcome to Google 2.0!\nI will try my best to answer all your questions here!\n\nType /cancel to cancel.' START_CHAT_TEXT ='Welcome chat' START_GAME_TEXT = 'Welcome game' +GOOGLE_HELP_TEXT = 'Start by typing a question!\n\nEg. "What is the meaning of life?" or "Why is the sky blue?"\n\nType /cancel to cancel.' def init_states(): states = ['GOOGLE', 'CHAT', 'GAME'] From 1685e590c4cb6a7c291818cacb7b2b8d2c44c647 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sun, 11 Jun 2023 11:06:57 +0800 Subject: [PATCH 026/117] chatbot conversation handler --- TelegramGPT/handlers.py | 41 +++++++++++++++++++++++++++++++++++------ TelegramGPT/utils.py | 6 +++++- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/TelegramGPT/handlers.py b/TelegramGPT/handlers.py index 0aac5ba3..733e05d1 100644 --- a/TelegramGPT/handlers.py +++ b/TelegramGPT/handlers.py @@ -7,13 +7,19 @@ async def _help(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.HELP_TEXT) +def decorator_help(func): + async def wrapper(update: Update, context: ContextTypes.DEFAULT_TYPE): + await func(update, context) + await _help(update, context) + return wrapper + +@decorator_help async def _invalid_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.INVALID_COMMAND_TEXT) - await _help(update, context) +@decorator_help async def _invalid_input(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.INVALID_INPUT_TEXT) - await _help(update, context) async def _cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CANCELLED_TEXT) @@ -29,9 +35,9 @@ async def _start_google(update: Update, context: ContextTypes.DEFAULT_TYPE): return utils.GOOGLE +@decorator_help async def _google_help(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.GOOGLE_HELP_TEXT) - await _help(update, context) async def _google(update: Update, context: ContextTypes.DEFAULT_TYPE): reply = query(update.effective_message.text) @@ -43,19 +49,29 @@ async def _start_chat(update: Update, context: ContextTypes.DEFAULT_TYPE): return utils.CHAT +@decorator_help async def _chat_help(update: Update, context: ContextTypes.DEFAULT_TYPE): pass -async def _chat(update: Update, context: ContextTypes.DEFAULT_TYPE): - text = 'Chat' +async def _chat_girl_1(update: Update, context: ContextTypes.DEFAULT_TYPE): + text = 'Chat girl 1' await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + return utils.CHAT_GIRL_1 + +async def _chat_custom(update: Update, context: ContextTypes.DEFAULT_TYPE): + text = 'Chat custom' + await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + + return utils.CHAT_CUSTOM + ############################ GAME ############################ async def _start_game(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_GAME_TEXT) return utils.GAME +@decorator_help async def _game_help(update: Update, context: ContextTypes.DEFAULT_TYPE): pass @@ -65,12 +81,25 @@ async def _game(update: Update, context: ContextTypes.DEFAULT_TYPE): def getHandlers(): utils.init_states() + + chat_handler = ConversationHandler( + entry_points=[CommandHandler('girl1', _chat_girl_1), CommandHandler('custom', _chat_custom)], + states={ + utils.CHAT_GIRL_1: [], + utils.CHAT_CUSTOM: [], + }, + fallbacks=[CommandHandler('cancel', _cancel)], + map_to_parent={ + ConversationHandler.END : ConversationHandler.END + } + ) + entry_points = [CommandHandler('google', _start_google), CommandHandler('chat', _start_chat), CommandHandler('game', _start_game)] main_handler = ConversationHandler( entry_points=entry_points, states={ utils.GOOGLE: [MessageHandler(filters.TEXT & ~filters.COMMAND, _google), CommandHandler('help', _google_help)], - utils.CHAT: [MessageHandler(filters.TEXT & ~filters.COMMAND, _chat), CommandHandler('help', _chat_help)], + utils.CHAT: [chat_handler, CommandHandler('help', _chat_help)], utils.GAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, _game), CommandHandler('help', _game_help)], }, fallbacks=[CommandHandler('cancel', _cancel)] + entry_points diff --git a/TelegramGPT/utils.py b/TelegramGPT/utils.py index 5d3661c4..79166830 100644 --- a/TelegramGPT/utils.py +++ b/TelegramGPT/utils.py @@ -8,15 +8,19 @@ INVALID_INPUT_TEXT = 'Invalid input!' CANCELLED_TEXT = 'Cancelled!' START_GOOGLE_TEXT = 'Welcome to Google 2.0!\nI will try my best to answer all your questions here!\n\nType /cancel to cancel.' -START_CHAT_TEXT ='Welcome chat' +START_CHAT_TEXT ='Welcome to Chat feature!\n\n/girl1 - basic girl chatbot\n/custom - custom chatbot\n\nType /cancel to cancel.' START_GAME_TEXT = 'Welcome game' GOOGLE_HELP_TEXT = 'Start by typing a question!\n\nEg. "What is the meaning of life?" or "Why is the sky blue?"\n\nType /cancel to cancel.' def init_states(): states = ['GOOGLE', 'CHAT', 'GAME'] + chat_states = ['CHAT_GIRL_1', 'CHAT_CUSTOM'] for val, state in enumerate(states): globals()[state] = val + + for val, chat_state in enumerate(chat_states): + globals()[chat_state] = val def getTelegramToken(): return os.getenv('TELEGRAM_TOKEN') From 4a609bd234b1ee23c11ff353f09c21b3ae97d0a1 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sat, 15 Jul 2023 10:48:11 +0800 Subject: [PATCH 027/117] .gitignore python boilerplate --- .gitignore | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3c32e91a..6769e21d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,160 @@ +# Byte-compiled / optimized / DLL files __pycache__/ +*.py[cod] +*$py.class -.env \ No newline at end of file +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file From 400c6c0ebbf5d5a4823b284abb29e9e6ac8f55dd Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sat, 15 Jul 2023 11:12:38 +0800 Subject: [PATCH 028/117] Add python requirements --- TelegramGPT/requirements.in | 3 ++ TelegramGPT/requirements.txt | 66 ++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 TelegramGPT/requirements.in create mode 100644 TelegramGPT/requirements.txt diff --git a/TelegramGPT/requirements.in b/TelegramGPT/requirements.in new file mode 100644 index 00000000..1845b02c --- /dev/null +++ b/TelegramGPT/requirements.in @@ -0,0 +1,3 @@ +openai==0.27.8 +python-dotenv==1.0.0 +python-telegram-bot==20.4 diff --git a/TelegramGPT/requirements.txt b/TelegramGPT/requirements.txt new file mode 100644 index 00000000..468d6451 --- /dev/null +++ b/TelegramGPT/requirements.txt @@ -0,0 +1,66 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile +# +aiohttp==3.8.4 + # via openai +aiosignal==1.3.1 + # via aiohttp +anyio==3.7.1 + # via httpcore +async-timeout==4.0.2 + # via aiohttp +attrs==23.1.0 + # via aiohttp +certifi==2023.5.7 + # via + # httpcore + # httpx + # requests +charset-normalizer==3.2.0 + # via + # aiohttp + # requests +colorama==0.4.6 + # via tqdm +frozenlist==1.4.0 + # via + # aiohttp + # aiosignal +h11==0.14.0 + # via httpcore +httpcore==0.17.3 + # via httpx +httpx==0.24.1 + # via python-telegram-bot +idna==3.4 + # via + # anyio + # httpx + # requests + # yarl +multidict==6.0.4 + # via + # aiohttp + # yarl +openai==0.27.8 + # via -r requirements.in +python-dotenv==1.0.0 + # via -r requirements.in +python-telegram-bot==20.4 + # via -r requirements.in +requests==2.31.0 + # via openai +sniffio==1.3.0 + # via + # anyio + # httpcore + # httpx +tqdm==4.65.0 + # via openai +urllib3==2.0.3 + # via requests +yarl==1.9.2 + # via aiohttp From 2d336cc3f8b88749fba19dd76ae3a96fabcdee71 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sat, 15 Jul 2023 11:26:31 +0800 Subject: [PATCH 029/117] Update instructions --- PYTHONREQUIREMENTS.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 PYTHONREQUIREMENTS.md diff --git a/PYTHONREQUIREMENTS.md b/PYTHONREQUIREMENTS.md new file mode 100644 index 00000000..ef8bc12a --- /dev/null +++ b/PYTHONREQUIREMENTS.md @@ -0,0 +1,16 @@ +## To install all Python dependencies for any specific project: + +1. cd into the project folder. +2. Ensure that the folder contains a `requirements.txt` file. This file contains all the necessary dependencies for this project to function. +3. If this file is not found, look at `Creating a requirements.txt file for your project`. Else, go to step 4. +4. Run the command `pip3 install -r requirements.txt` to generate the dependency file. + +
+ +## Creating a requirements.txt file for your project: + +1. We will use two libraries to help us generate the `requirements.txt` file - pipreqs and pip-tools. +2. If not already installed, run `pip3 install pipreqs` and `pip3 install pip-tools`. +3. cd into the project folder. +4. Run the command `pipreqs --savepath=requirements.in && pip-compile`. +5. Both `requirements.in` and `requirements.txt` files should be created. \ No newline at end of file From 9a6efc9cb05b2d01fba6fb5dbe5efcd40ff73475 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sat, 15 Jul 2023 11:56:53 +0800 Subject: [PATCH 030/117] Update requirements.txt --- IMDB_Movie_Scraper/requirements.in | 1 + IMDB_Movie_Scraper/requirements.txt | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 IMDB_Movie_Scraper/requirements.in create mode 100644 IMDB_Movie_Scraper/requirements.txt diff --git a/IMDB_Movie_Scraper/requirements.in b/IMDB_Movie_Scraper/requirements.in new file mode 100644 index 00000000..68f64703 --- /dev/null +++ b/IMDB_Movie_Scraper/requirements.in @@ -0,0 +1 @@ +beautifulsoup4==4.12.2 diff --git a/IMDB_Movie_Scraper/requirements.txt b/IMDB_Movie_Scraper/requirements.txt new file mode 100644 index 00000000..93f8641c --- /dev/null +++ b/IMDB_Movie_Scraper/requirements.txt @@ -0,0 +1,10 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile +# +beautifulsoup4==4.12.2 + # via -r requirements.in +soupsieve==2.4.1 + # via beautifulsoup4 From 4ccf71cf53474b09bfd9fbcc08075b2ab2b068fc Mon Sep 17 00:00:00 2001 From: thamruicong Date: Sat, 15 Jul 2023 14:58:22 +0800 Subject: [PATCH 031/117] Stash --- TelegramGPT/handlers.py | 17 ++++++++++++----- TelegramGPT/utils.py | 7 ++++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/TelegramGPT/handlers.py b/TelegramGPT/handlers.py index 733e05d1..1059a28a 100644 --- a/TelegramGPT/handlers.py +++ b/TelegramGPT/handlers.py @@ -51,11 +51,15 @@ async def _start_chat(update: Update, context: ContextTypes.DEFAULT_TYPE): @decorator_help async def _chat_help(update: Update, context: ContextTypes.DEFAULT_TYPE): - pass + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CHAT_HELP_TEXT) async def _chat_girl_1(update: Update, context: ContextTypes.DEFAULT_TYPE): - text = 'Chat girl 1' - await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_CHAT_GIRL_1_TEXT) + + # TODO: Need to somehow initilize the chatbot and use it for subsequent _chat methods + # Also, need a way for chatbot to remember chat history + # One solution is to keep track of the entire history here and send it together with the prompt + # But this solution consumes alot of tokens return utils.CHAT_GIRL_1 @@ -65,6 +69,9 @@ async def _chat_custom(update: Update, context: ContextTypes.DEFAULT_TYPE): return utils.CHAT_CUSTOM +async def _chat(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=update.effective_message.text) + ############################ GAME ############################ async def _start_game(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_GAME_TEXT) @@ -85,8 +92,8 @@ def getHandlers(): chat_handler = ConversationHandler( entry_points=[CommandHandler('girl1', _chat_girl_1), CommandHandler('custom', _chat_custom)], states={ - utils.CHAT_GIRL_1: [], - utils.CHAT_CUSTOM: [], + utils.CHAT_GIRL_1: [MessageHandler(filters.TEXT & ~filters.COMMAND, _chat)], + utils.CHAT_CUSTOM: [MessageHandler(filters.TEXT & ~filters.COMMAND, _chat)], }, fallbacks=[CommandHandler('cancel', _cancel)], map_to_parent={ diff --git a/TelegramGPT/utils.py b/TelegramGPT/utils.py index 79166830..09372cfe 100644 --- a/TelegramGPT/utils.py +++ b/TelegramGPT/utils.py @@ -7,10 +7,15 @@ INVALID_COMMAND_TEXT = 'Invalid command!' INVALID_INPUT_TEXT = 'Invalid input!' CANCELLED_TEXT = 'Cancelled!' + START_GOOGLE_TEXT = 'Welcome to Google 2.0!\nI will try my best to answer all your questions here!\n\nType /cancel to cancel.' +GOOGLE_HELP_TEXT = 'Start by typing a question!\n\nEg. "What is the meaning of life?" or "Why is the sky blue?"\n\nType /cancel to cancel.' + START_CHAT_TEXT ='Welcome to Chat feature!\n\n/girl1 - basic girl chatbot\n/custom - custom chatbot\n\nType /cancel to cancel.' +CHAT_HELP_TEXT = 'Start by striking up a conversation!\n\nEg. "How are you?" or "What is your name?"\n\nType /cancel to cancel.' +START_CHAT_GIRL_1_TEXT = 'You have selected preset Chat Girl 1. Happy chatting!\n\nType /cancel to cancel.' + START_GAME_TEXT = 'Welcome game' -GOOGLE_HELP_TEXT = 'Start by typing a question!\n\nEg. "What is the meaning of life?" or "Why is the sky blue?"\n\nType /cancel to cancel.' def init_states(): states = ['GOOGLE', 'CHAT', 'GAME'] From 4623e6ec21dfd1912ef9f31bc00bf321be971865 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Wed, 2 Aug 2023 12:37:10 +0800 Subject: [PATCH 032/117] Refactor handlers into own files --- TelegramGPT/handlers.py | 120 ------------------------- TelegramGPT/handlers/chat_handler.py | 32 +++++++ TelegramGPT/handlers/game_handler.py | 17 ++++ TelegramGPT/handlers/google_handler.py | 18 ++++ TelegramGPT/handlers/main_handler.py | 39 ++++++++ TelegramGPT/handlers/util_handler.py | 28 ++++++ TelegramGPT/main.py | 2 +- 7 files changed, 135 insertions(+), 121 deletions(-) delete mode 100644 TelegramGPT/handlers.py create mode 100644 TelegramGPT/handlers/chat_handler.py create mode 100644 TelegramGPT/handlers/game_handler.py create mode 100644 TelegramGPT/handlers/google_handler.py create mode 100644 TelegramGPT/handlers/main_handler.py create mode 100644 TelegramGPT/handlers/util_handler.py diff --git a/TelegramGPT/handlers.py b/TelegramGPT/handlers.py deleted file mode 100644 index 1059a28a..00000000 --- a/TelegramGPT/handlers.py +++ /dev/null @@ -1,120 +0,0 @@ -from telegram import Update -from telegram.ext import CommandHandler, MessageHandler, filters, ContextTypes, ConversationHandler -import utils -from generate import query - -############################ UTILS ############################ -async def _help(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.HELP_TEXT) - -def decorator_help(func): - async def wrapper(update: Update, context: ContextTypes.DEFAULT_TYPE): - await func(update, context) - await _help(update, context) - return wrapper - -@decorator_help -async def _invalid_command(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.INVALID_COMMAND_TEXT) - -@decorator_help -async def _invalid_input(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.INVALID_INPUT_TEXT) - -async def _cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CANCELLED_TEXT) - - return ConversationHandler.END - -async def _ignore(update: Update, context: ContextTypes.DEFAULT_TYPE): - pass - -############################ GOOGLE ############################ -async def _start_google(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_GOOGLE_TEXT) - - return utils.GOOGLE - -@decorator_help -async def _google_help(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.GOOGLE_HELP_TEXT) - -async def _google(update: Update, context: ContextTypes.DEFAULT_TYPE): - reply = query(update.effective_message.text) - await context.bot.send_message(chat_id=update.effective_chat.id, text=reply) - -############################ CHAT ############################ -async def _start_chat(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_CHAT_TEXT) - - return utils.CHAT - -@decorator_help -async def _chat_help(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CHAT_HELP_TEXT) - -async def _chat_girl_1(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_CHAT_GIRL_1_TEXT) - - # TODO: Need to somehow initilize the chatbot and use it for subsequent _chat methods - # Also, need a way for chatbot to remember chat history - # One solution is to keep track of the entire history here and send it together with the prompt - # But this solution consumes alot of tokens - - return utils.CHAT_GIRL_1 - -async def _chat_custom(update: Update, context: ContextTypes.DEFAULT_TYPE): - text = 'Chat custom' - await context.bot.send_message(chat_id=update.effective_chat.id, text=text) - - return utils.CHAT_CUSTOM - -async def _chat(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=update.effective_message.text) - -############################ GAME ############################ -async def _start_game(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_GAME_TEXT) - - return utils.GAME - -@decorator_help -async def _game_help(update: Update, context: ContextTypes.DEFAULT_TYPE): - pass - -async def _game(update: Update, context: ContextTypes.DEFAULT_TYPE): - text = 'Game' - await context.bot.send_message(chat_id=update.effective_chat.id, text=text) - -def getHandlers(): - utils.init_states() - - chat_handler = ConversationHandler( - entry_points=[CommandHandler('girl1', _chat_girl_1), CommandHandler('custom', _chat_custom)], - states={ - utils.CHAT_GIRL_1: [MessageHandler(filters.TEXT & ~filters.COMMAND, _chat)], - utils.CHAT_CUSTOM: [MessageHandler(filters.TEXT & ~filters.COMMAND, _chat)], - }, - fallbacks=[CommandHandler('cancel', _cancel)], - map_to_parent={ - ConversationHandler.END : ConversationHandler.END - } - ) - - entry_points = [CommandHandler('google', _start_google), CommandHandler('chat', _start_chat), CommandHandler('game', _start_game)] - main_handler = ConversationHandler( - entry_points=entry_points, - states={ - utils.GOOGLE: [MessageHandler(filters.TEXT & ~filters.COMMAND, _google), CommandHandler('help', _google_help)], - utils.CHAT: [chat_handler, CommandHandler('help', _chat_help)], - utils.GAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, _game), CommandHandler('help', _game_help)], - }, - fallbacks=[CommandHandler('cancel', _cancel)] + entry_points - ) - - ignore_handler = MessageHandler(filters.UpdateType.EDITED, _ignore) - help_command_handler = CommandHandler('help', _help) - invalid_command_handler = MessageHandler(filters.COMMAND, _invalid_command) - invalid_input_handler = MessageHandler(filters.ALL, _invalid_input) - - return [ignore_handler, main_handler, help_command_handler, invalid_command_handler, invalid_input_handler] \ No newline at end of file diff --git a/TelegramGPT/handlers/chat_handler.py b/TelegramGPT/handlers/chat_handler.py new file mode 100644 index 00000000..308edb8a --- /dev/null +++ b/TelegramGPT/handlers/chat_handler.py @@ -0,0 +1,32 @@ +from telegram import Update +from telegram.ext import ContextTypes +import utils +from handlers.util_handler import decorator_help + +async def _start_chat(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_CHAT_TEXT) + + return utils.CHAT + +@decorator_help +async def _chat_help(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CHAT_HELP_TEXT) + +async def _chat_girl_1(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_CHAT_GIRL_1_TEXT) + + # TODO: Need to somehow initilize the chatbot and use it for subsequent _chat methods + # Also, need a way for chatbot to remember chat history + # One solution is to keep track of the entire history here and send it together with the prompt + # But this solution consumes alot of tokens + + return utils.CHAT_GIRL_1 + +async def _chat_custom(update: Update, context: ContextTypes.DEFAULT_TYPE): + text = 'Chat custom' + await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + + return utils.CHAT_CUSTOM + +async def _chat(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=update.effective_message.text) \ No newline at end of file diff --git a/TelegramGPT/handlers/game_handler.py b/TelegramGPT/handlers/game_handler.py new file mode 100644 index 00000000..d5634daa --- /dev/null +++ b/TelegramGPT/handlers/game_handler.py @@ -0,0 +1,17 @@ +from telegram import Update +from telegram.ext import ContextTypes +import utils +from handlers.util_handler import decorator_help + +async def _start_game(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_GAME_TEXT) + + return utils.GAME + +@decorator_help +async def _game_help(update: Update, context: ContextTypes.DEFAULT_TYPE): + pass + +async def _game(update: Update, context: ContextTypes.DEFAULT_TYPE): + text = 'Game' + await context.bot.send_message(chat_id=update.effective_chat.id, text=text) \ No newline at end of file diff --git a/TelegramGPT/handlers/google_handler.py b/TelegramGPT/handlers/google_handler.py new file mode 100644 index 00000000..b7e138fd --- /dev/null +++ b/TelegramGPT/handlers/google_handler.py @@ -0,0 +1,18 @@ +from telegram import Update +from telegram.ext import ContextTypes +import utils +from handlers.util_handler import decorator_help +from generate import query + +async def _start_google(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_GOOGLE_TEXT) + + return utils.GOOGLE + +@decorator_help +async def _google_help(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.GOOGLE_HELP_TEXT) + +async def _google(update: Update, context: ContextTypes.DEFAULT_TYPE): + reply = query(update.effective_message.text) + await context.bot.send_message(chat_id=update.effective_chat.id, text=reply) diff --git a/TelegramGPT/handlers/main_handler.py b/TelegramGPT/handlers/main_handler.py new file mode 100644 index 00000000..2d887ffa --- /dev/null +++ b/TelegramGPT/handlers/main_handler.py @@ -0,0 +1,39 @@ +from telegram.ext import CommandHandler, MessageHandler, filters, ConversationHandler +import utils +import handlers.chat_handler as chath +import handlers.game_handler as gameh +import handlers.google_handler as googleh +import handlers.util_handler as utilh + +def getHandlers(): + utils.init_states() + + chat_handler = ConversationHandler( + entry_points=[CommandHandler('girl1', chath._chat_girl_1), CommandHandler('custom', chath._chat_custom)], + states={ + utils.CHAT_GIRL_1: [MessageHandler(filters.TEXT & ~filters.COMMAND, chath._chat)], + utils.CHAT_CUSTOM: [MessageHandler(filters.TEXT & ~filters.COMMAND, chath._chat)], + }, + fallbacks=[CommandHandler('cancel', utilh._cancel)], + map_to_parent={ + ConversationHandler.END : ConversationHandler.END + } + ) + + entry_points = [CommandHandler('google', googleh._start_google), CommandHandler('chat', chath._start_chat), CommandHandler('game', gameh._start_game)] + main_handler = ConversationHandler( + entry_points=entry_points, + states={ + utils.GOOGLE: [MessageHandler(filters.TEXT & ~filters.COMMAND, googleh._google), CommandHandler('help', googleh._google_help)], + utils.CHAT: [chat_handler, CommandHandler('help', chath._chat_help)], + utils.GAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, gameh._game), CommandHandler('help', gameh._game_help)], + }, + fallbacks=[CommandHandler('cancel', utilh._cancel)] + entry_points + ) + + ignore_handler = MessageHandler(filters.UpdateType.EDITED, utilh._ignore) + help_command_handler = CommandHandler('help', utilh._help) + invalid_command_handler = MessageHandler(filters.COMMAND, utilh._invalid_command) + invalid_input_handler = MessageHandler(filters.ALL, utilh._invalid_input) + + return [ignore_handler, main_handler, help_command_handler, invalid_command_handler, invalid_input_handler] \ No newline at end of file diff --git a/TelegramGPT/handlers/util_handler.py b/TelegramGPT/handlers/util_handler.py new file mode 100644 index 00000000..f22009f3 --- /dev/null +++ b/TelegramGPT/handlers/util_handler.py @@ -0,0 +1,28 @@ +from telegram import Update +from telegram.ext import ContextTypes, ConversationHandler +import utils + +async def _help(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.HELP_TEXT) + +def decorator_help(func): + async def wrapper(update: Update, context: ContextTypes.DEFAULT_TYPE): + await func(update, context) + await _help(update, context) + return wrapper + +@decorator_help +async def _invalid_command(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.INVALID_COMMAND_TEXT) + +@decorator_help +async def _invalid_input(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.INVALID_INPUT_TEXT) + +async def _cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CANCELLED_TEXT) + + return ConversationHandler.END + +async def _ignore(update: Update, context: ContextTypes.DEFAULT_TYPE): + pass \ No newline at end of file diff --git a/TelegramGPT/main.py b/TelegramGPT/main.py index f84cc096..4a624c98 100644 --- a/TelegramGPT/main.py +++ b/TelegramGPT/main.py @@ -1,7 +1,7 @@ import logging from telegram.ext import ApplicationBuilder -from handlers import getHandlers +from handlers.main_handler import getHandlers from utils import getTelegramToken logging.basicConfig( From db35d46d8837980c9e5ec83d65ac481b72a56e01 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Wed, 2 Aug 2023 12:47:20 +0800 Subject: [PATCH 033/117] Add utils folder --- TelegramGPT/handlers/chat_handler.py | 2 +- TelegramGPT/handlers/game_handler.py | 2 +- TelegramGPT/handlers/google_handler.py | 4 ++-- TelegramGPT/handlers/main_handler.py | 2 +- TelegramGPT/handlers/util_handler.py | 2 +- TelegramGPT/main.py | 2 +- TelegramGPT/{ => utils}/generate.py | 2 +- TelegramGPT/{ => utils}/utils.py | 0 8 files changed, 8 insertions(+), 8 deletions(-) rename TelegramGPT/{ => utils}/generate.py (90%) rename TelegramGPT/{ => utils}/utils.py (100%) diff --git a/TelegramGPT/handlers/chat_handler.py b/TelegramGPT/handlers/chat_handler.py index 308edb8a..787ebb1f 100644 --- a/TelegramGPT/handlers/chat_handler.py +++ b/TelegramGPT/handlers/chat_handler.py @@ -1,6 +1,6 @@ from telegram import Update from telegram.ext import ContextTypes -import utils +import utils.utils as utils from handlers.util_handler import decorator_help async def _start_chat(update: Update, context: ContextTypes.DEFAULT_TYPE): diff --git a/TelegramGPT/handlers/game_handler.py b/TelegramGPT/handlers/game_handler.py index d5634daa..9fcbb319 100644 --- a/TelegramGPT/handlers/game_handler.py +++ b/TelegramGPT/handlers/game_handler.py @@ -1,6 +1,6 @@ from telegram import Update from telegram.ext import ContextTypes -import utils +import utils.utils as utils from handlers.util_handler import decorator_help async def _start_game(update: Update, context: ContextTypes.DEFAULT_TYPE): diff --git a/TelegramGPT/handlers/google_handler.py b/TelegramGPT/handlers/google_handler.py index b7e138fd..03e7c527 100644 --- a/TelegramGPT/handlers/google_handler.py +++ b/TelegramGPT/handlers/google_handler.py @@ -1,8 +1,8 @@ from telegram import Update from telegram.ext import ContextTypes -import utils +import utils.utils as utils from handlers.util_handler import decorator_help -from generate import query +from utils.generate import query async def _start_google(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_GOOGLE_TEXT) diff --git a/TelegramGPT/handlers/main_handler.py b/TelegramGPT/handlers/main_handler.py index 2d887ffa..e53dc772 100644 --- a/TelegramGPT/handlers/main_handler.py +++ b/TelegramGPT/handlers/main_handler.py @@ -1,5 +1,5 @@ from telegram.ext import CommandHandler, MessageHandler, filters, ConversationHandler -import utils +import utils.utils as utils import handlers.chat_handler as chath import handlers.game_handler as gameh import handlers.google_handler as googleh diff --git a/TelegramGPT/handlers/util_handler.py b/TelegramGPT/handlers/util_handler.py index f22009f3..ffe6b608 100644 --- a/TelegramGPT/handlers/util_handler.py +++ b/TelegramGPT/handlers/util_handler.py @@ -1,6 +1,6 @@ from telegram import Update from telegram.ext import ContextTypes, ConversationHandler -import utils +import utils.utils as utils async def _help(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.HELP_TEXT) diff --git a/TelegramGPT/main.py b/TelegramGPT/main.py index 4a624c98..7346f36e 100644 --- a/TelegramGPT/main.py +++ b/TelegramGPT/main.py @@ -2,7 +2,7 @@ from telegram.ext import ApplicationBuilder from handlers.main_handler import getHandlers -from utils import getTelegramToken +from utils.utils import getTelegramToken logging.basicConfig( format='%(asctime)s : %(name)s : %(levelname)s : %(message)s', diff --git a/TelegramGPT/generate.py b/TelegramGPT/utils/generate.py similarity index 90% rename from TelegramGPT/generate.py rename to TelegramGPT/utils/generate.py index dafb58f9..5e1d3c6a 100644 --- a/TelegramGPT/generate.py +++ b/TelegramGPT/utils/generate.py @@ -1,5 +1,5 @@ import openai -from utils import getOpenAIAPIKey +from utils.utils import getOpenAIAPIKey import logging log = logging.getLogger(__name__) diff --git a/TelegramGPT/utils.py b/TelegramGPT/utils/utils.py similarity index 100% rename from TelegramGPT/utils.py rename to TelegramGPT/utils/utils.py From 6b9ac090f7ab74a65eab5567af8bcfe136cb0416 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Thu, 3 Aug 2023 01:03:13 +0800 Subject: [PATCH 034/117] Chatbot with chathistory --- TelegramGPT/handlers/chat_handler.py | 31 ++++++++++----- TelegramGPT/handlers/main_handler.py | 4 +- TelegramGPT/utils/chatbot.py | 58 ++++++++++++++++++++++++++++ TelegramGPT/utils/generate.py | 14 ++++++- TelegramGPT/utils/utils.py | 5 ++- 5 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 TelegramGPT/utils/chatbot.py diff --git a/TelegramGPT/handlers/chat_handler.py b/TelegramGPT/handlers/chat_handler.py index 787ebb1f..7dbdbc11 100644 --- a/TelegramGPT/handlers/chat_handler.py +++ b/TelegramGPT/handlers/chat_handler.py @@ -2,6 +2,7 @@ from telegram.ext import ContextTypes import utils.utils as utils from handlers.util_handler import decorator_help +from utils.chatbot import ChatBot async def _start_chat(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_CHAT_TEXT) @@ -15,18 +16,28 @@ async def _chat_help(update: Update, context: ContextTypes.DEFAULT_TYPE): async def _chat_girl_1(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_CHAT_GIRL_1_TEXT) - # TODO: Need to somehow initilize the chatbot and use it for subsequent _chat methods - # Also, need a way for chatbot to remember chat history - # One solution is to keep track of the entire history here and send it together with the prompt - # But this solution consumes alot of tokens - - return utils.CHAT_GIRL_1 + ChatBot.build(utils.CHAT_GIRL_1_PROMPT) + return utils.CHAT_MODE async def _chat_custom(update: Update, context: ContextTypes.DEFAULT_TYPE): - text = 'Chat custom' - await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_CHAT_CUSTOM_TEXT) + + return utils.CHAT_CUSTOM_CONFIG + +async def _chat_custom_config(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CHAT_CUSTOM_CONFIG_TEXT) - return utils.CHAT_CUSTOM + ChatBot.build(update.effective_message.text) + return utils.CHAT_MODE async def _chat(update: Update, context: ContextTypes.DEFAULT_TYPE): - await context.bot.send_message(chat_id=update.effective_chat.id, text=update.effective_message.text) \ No newline at end of file + reply = ChatBot.generate_response(update.effective_message.text) + await context.bot.send_message(chat_id=update.effective_chat.id, text=reply) + +# Use this to save the chatbot history +# TODO +async def _chat_cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): + text = 'Confirm cancel chat\nNote that this will delete all chat history\n\nType /cancel to confirm or /save to save the chatbot history and cancel' + await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + + # return ConversationHandler.END \ No newline at end of file diff --git a/TelegramGPT/handlers/main_handler.py b/TelegramGPT/handlers/main_handler.py index e53dc772..2ddd1ae0 100644 --- a/TelegramGPT/handlers/main_handler.py +++ b/TelegramGPT/handlers/main_handler.py @@ -11,8 +11,8 @@ def getHandlers(): chat_handler = ConversationHandler( entry_points=[CommandHandler('girl1', chath._chat_girl_1), CommandHandler('custom', chath._chat_custom)], states={ - utils.CHAT_GIRL_1: [MessageHandler(filters.TEXT & ~filters.COMMAND, chath._chat)], - utils.CHAT_CUSTOM: [MessageHandler(filters.TEXT & ~filters.COMMAND, chath._chat)], + utils.CHAT_CUSTOM_CONFIG: [MessageHandler(filters.TEXT & ~filters.COMMAND, chath._chat_custom_config)], + utils.CHAT_MODE: [MessageHandler(filters.TEXT & ~filters.COMMAND, chath._chat)], }, fallbacks=[CommandHandler('cancel', utilh._cancel)], map_to_parent={ diff --git a/TelegramGPT/utils/chatbot.py b/TelegramGPT/utils/chatbot.py new file mode 100644 index 00000000..be761edb --- /dev/null +++ b/TelegramGPT/utils/chatbot.py @@ -0,0 +1,58 @@ +# Static class that represents a class instance of the chatbot +# Only one chatbot can be built at a time + +# Features: + # 1. Create the chatbot (this simply creates a new instance of the chatbot) + # 2. Build the chatbot with the given prompt + # 3. Generate a response from the chatbot + # 4. Chatbot remembers chat history + # 5. Chatbot can be saved and loaded (to save on tokens) TODO + +import logging +from utils.generate import chat + +log = logging.getLogger(__name__) + +class ChatBot(): + chathistory = None + + def __init__(self): + raise Exception('ChatBot is a static class and should not be instantiated') + + @classmethod + def build(cls, prompt): + ChatBot.chathistory = [] + + ChatBot.chathistory.append({ + 'role': 'system', + 'content': prompt, + }) + + return ChatBot + + @classmethod + def generate_response(cls, input): + if ChatBot.chathistory is None: + raise Exception('Chatbot has not been built yet') + + ChatBot.chathistory.append({ + 'role': 'user', + 'content': input, + }) + + response = chat(ChatBot.chathistory) + + ChatBot.chathistory.append({ + 'role': 'assistant', + 'content': response, + }) + + return response + + @classmethod + def save_chatbot(cls): + pass + + @classmethod + def load_chatbot(cls): + pass diff --git a/TelegramGPT/utils/generate.py b/TelegramGPT/utils/generate.py index 5e1d3c6a..d04741a8 100644 --- a/TelegramGPT/utils/generate.py +++ b/TelegramGPT/utils/generate.py @@ -5,7 +5,7 @@ log = logging.getLogger(__name__) openai.api_key = getOpenAIAPIKey() -MAX_TOKENS = 50 +MAX_TOKENS = 100 def query(prompt, model='text-davinci-003'): response = openai.Completion.create( @@ -15,4 +15,14 @@ def query(prompt, model='text-davinci-003'): temperature=0.3, ) log.info(response) - return response.choices[0].text.strip() \ No newline at end of file + return response.choices[0].text.strip() + +def chat(messages, model='gpt-3.5-turbo'): + response = openai.ChatCompletion.create( + model=model, + messages=messages, + max_tokens=MAX_TOKENS, + temperature=0.8, + ) + log.info(response) + return response.choices[0].message.content.strip() \ No newline at end of file diff --git a/TelegramGPT/utils/utils.py b/TelegramGPT/utils/utils.py index 09372cfe..bab75b33 100644 --- a/TelegramGPT/utils/utils.py +++ b/TelegramGPT/utils/utils.py @@ -14,12 +14,15 @@ START_CHAT_TEXT ='Welcome to Chat feature!\n\n/girl1 - basic girl chatbot\n/custom - custom chatbot\n\nType /cancel to cancel.' CHAT_HELP_TEXT = 'Start by striking up a conversation!\n\nEg. "How are you?" or "What is your name?"\n\nType /cancel to cancel.' START_CHAT_GIRL_1_TEXT = 'You have selected preset Chat Girl 1. Happy chatting!\n\nType /cancel to cancel.' +CHAT_GIRL_1_PROMPT = 'Act as if you are a friendly teenage girl.' +START_CHAT_CUSTOM_TEXT = 'You have selected custom chatbot. Type your prompt below!\n\nType /cancel to cancel.' +CHAT_CUSTOM_CONFIG_TEXT = 'Custom chatbot prompt initialized. Happy chatting!\n\nType /cancel to cancel.' START_GAME_TEXT = 'Welcome game' def init_states(): states = ['GOOGLE', 'CHAT', 'GAME'] - chat_states = ['CHAT_GIRL_1', 'CHAT_CUSTOM'] + chat_states = ['CHAT_MODE', 'CHAT_CUSTOM_CONFIG'] for val, state in enumerate(states): globals()[state] = val From 8779881877a968ebc0a71417634cd1fe4e53a8fe Mon Sep 17 00:00:00 2001 From: thamruicong Date: Thu, 3 Aug 2023 11:24:30 +0800 Subject: [PATCH 035/117] Load and save chatbot history --- TelegramGPT/handlers/chat_handler.py | 30 ++++++++++++++++++++----- TelegramGPT/handlers/main_handler.py | 5 +++-- TelegramGPT/utils/chatbot.py | 33 +++++++++++++++++++++++----- TelegramGPT/utils/utils.py | 7 +++++- 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/TelegramGPT/handlers/chat_handler.py b/TelegramGPT/handlers/chat_handler.py index 7dbdbc11..733d20e5 100644 --- a/TelegramGPT/handlers/chat_handler.py +++ b/TelegramGPT/handlers/chat_handler.py @@ -1,12 +1,16 @@ from telegram import Update -from telegram.ext import ContextTypes +from telegram.ext import ContextTypes, ConversationHandler import utils.utils as utils from handlers.util_handler import decorator_help from utils.chatbot import ChatBot +import handlers.util_handler as utilh async def _start_chat(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_CHAT_TEXT) + if (ChatBot.has_chatbot()): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.START_CHAT_LOAD_TEXT) + return utils.CHAT @decorator_help @@ -34,10 +38,24 @@ async def _chat(update: Update, context: ContextTypes.DEFAULT_TYPE): reply = ChatBot.generate_response(update.effective_message.text) await context.bot.send_message(chat_id=update.effective_chat.id, text=reply) -# Use this to save the chatbot history -# TODO async def _chat_cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): - text = 'Confirm cancel chat\nNote that this will delete all chat history\n\nType /cancel to confirm or /save to save the chatbot history and cancel' - await context.bot.send_message(chat_id=update.effective_chat.id, text=text) + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CHAT_CANCEL_TEXT) + + return utils.CHAT_CANCEL + +# Use this to save the chatbot history, if any +async def _chat_save(update: Update, context: ContextTypes.DEFAULT_TYPE): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CHAT_SAVE_TEXT) + + ChatBot.save_chatbot() + return await utilh._cancel(update, context) + +# Use this to load the chatbot history, if any +async def _chat_load(update: Update, context: ContextTypes.DEFAULT_TYPE): + if (ChatBot.has_chatbot()): + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CHAT_LOAD_TEXT) - # return ConversationHandler.END \ No newline at end of file + ChatBot.load_chatbot() + return utils.CHAT_MODE + else: + await context.bot.send_message(chat_id=update.effective_chat.id, text=utils.CHAT_LOAD_ERROR_TEXT) \ No newline at end of file diff --git a/TelegramGPT/handlers/main_handler.py b/TelegramGPT/handlers/main_handler.py index 2ddd1ae0..85d79beb 100644 --- a/TelegramGPT/handlers/main_handler.py +++ b/TelegramGPT/handlers/main_handler.py @@ -9,10 +9,11 @@ def getHandlers(): utils.init_states() chat_handler = ConversationHandler( - entry_points=[CommandHandler('girl1', chath._chat_girl_1), CommandHandler('custom', chath._chat_custom)], + entry_points=[CommandHandler('girl1', chath._chat_girl_1), CommandHandler('custom', chath._chat_custom), CommandHandler('load', chath._chat_load)], states={ utils.CHAT_CUSTOM_CONFIG: [MessageHandler(filters.TEXT & ~filters.COMMAND, chath._chat_custom_config)], - utils.CHAT_MODE: [MessageHandler(filters.TEXT & ~filters.COMMAND, chath._chat)], + utils.CHAT_MODE: [MessageHandler(filters.TEXT & ~filters.COMMAND, chath._chat), CommandHandler('cancel', chath._chat_cancel)], + utils.CHAT_CANCEL: [CommandHandler('save', chath._chat_save)] }, fallbacks=[CommandHandler('cancel', utilh._cancel)], map_to_parent={ diff --git a/TelegramGPT/utils/chatbot.py b/TelegramGPT/utils/chatbot.py index be761edb..8d8ff65a 100644 --- a/TelegramGPT/utils/chatbot.py +++ b/TelegramGPT/utils/chatbot.py @@ -6,12 +6,13 @@ # 2. Build the chatbot with the given prompt # 3. Generate a response from the chatbot # 4. Chatbot remembers chat history - # 5. Chatbot can be saved and loaded (to save on tokens) TODO + # 5. Chatbot can be saved and loaded (to save on tokens) -import logging from utils.generate import chat +import json +import os -log = logging.getLogger(__name__) +save_file_name = 'chatbot.json' class ChatBot(): chathistory = None @@ -51,8 +52,30 @@ def generate_response(cls, input): @classmethod def save_chatbot(cls): - pass + if ChatBot.chathistory is None: + raise Exception('Chatbot has not been built yet') + + try: + with open(save_file_name, 'w') as f: + f.write(json.dumps(ChatBot.chathistory, indent=4)) + except Exception as e: + raise Exception(e) + + return ChatBot @classmethod def load_chatbot(cls): - pass + if (not ChatBot.has_chatbot()): + raise Exception('Chatbot does not exist') + + try: + with open(save_file_name, 'r') as f: + ChatBot.chathistory = json.loads(f.read()) + except Exception as e: + raise Exception(e) + + return ChatBot + + @classmethod + def has_chatbot(cls): + return os.path.isfile(save_file_name) \ No newline at end of file diff --git a/TelegramGPT/utils/utils.py b/TelegramGPT/utils/utils.py index bab75b33..2b13c58b 100644 --- a/TelegramGPT/utils/utils.py +++ b/TelegramGPT/utils/utils.py @@ -17,12 +17,17 @@ CHAT_GIRL_1_PROMPT = 'Act as if you are a friendly teenage girl.' START_CHAT_CUSTOM_TEXT = 'You have selected custom chatbot. Type your prompt below!\n\nType /cancel to cancel.' CHAT_CUSTOM_CONFIG_TEXT = 'Custom chatbot prompt initialized. Happy chatting!\n\nType /cancel to cancel.' +CHAT_CANCEL_TEXT = 'Confirm cancel chat?\nNote that this will delete all chat history.\n\nType /cancel to cancel or /save to save the chatbot history and cancel' +CHAT_SAVE_TEXT = 'Chatbot history saved!' +CHAT_LOAD_TEXT = 'Chatbot history loaded!' +START_CHAT_LOAD_TEXT = 'Previous chatbot history available.\n\nType /load to load.' +CHAT_LOAD_ERROR_TEXT = 'No previous chatbot history available.' START_GAME_TEXT = 'Welcome game' def init_states(): states = ['GOOGLE', 'CHAT', 'GAME'] - chat_states = ['CHAT_MODE', 'CHAT_CUSTOM_CONFIG'] + chat_states = ['CHAT_MODE', 'CHAT_CUSTOM_CONFIG', 'CHAT_CANCEL'] for val, state in enumerate(states): globals()[state] = val From 3d0e4fcad5d04b48dc0d2f491ae234e246794844 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Thu, 3 Aug 2023 11:25:04 +0800 Subject: [PATCH 036/117] Update .gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6769e21d..7493b832 100644 --- a/.gitignore +++ b/.gitignore @@ -157,4 +157,7 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ + +# TelegramGPT +chatbot.json \ No newline at end of file From 75d6bf45718a9fddbda4be7eb1d0708901213e03 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Thu, 3 Aug 2023 12:30:22 +0800 Subject: [PATCH 037/117] Remove unused module --- TelegramGPT/handlers/chat_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TelegramGPT/handlers/chat_handler.py b/TelegramGPT/handlers/chat_handler.py index 733d20e5..63e9f4f9 100644 --- a/TelegramGPT/handlers/chat_handler.py +++ b/TelegramGPT/handlers/chat_handler.py @@ -1,5 +1,5 @@ from telegram import Update -from telegram.ext import ContextTypes, ConversationHandler +from telegram.ext import ContextTypes import utils.utils as utils from handlers.util_handler import decorator_help from utils.chatbot import ChatBot From 1440a73e113c674a7d54f44b35a2ecc5321671d3 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Thu, 3 Aug 2023 12:37:52 +0800 Subject: [PATCH 038/117] Complete TelegramGPT --- README.md | 2 +- TelegramGPT/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f3119b7..06f50cd6 100644 --- a/README.md +++ b/README.md @@ -401,7 +401,7 @@ To get started, simply fork this repo. Please refer to [CONTRIBUTING.md](CONTRIB - [How to Make a Reddit Bot - YouTube](https://www.youtube.com/watch?v=krTUf7BpTc0) (video) - [Build a Facebook Messenger Bot](https://blog.hartleybrody.com/fb-messenger-bot/) - [Making a Reddit + Facebook Messenger Bot](https://pythontips.com/2017/04/13/making-a-reddit-facebook-messenger-bot/) -- How To Create a Telegram Bot Using Python +- How To Create a Telegram Bot Using Python :white_check_mark: - [Part 1](https://khashtamov.com/en/how-to-create-a-telegram-bot-using-python/) - [Part 2](https://khashtamov.com/en/how-to-deploy-telegram-bot-django/) - [Create a Twitter Bot In Python](https://medium.freecodecamp.org/creating-a-twitter-bot-in-python-with-tweepy-ac524157a607) diff --git a/TelegramGPT/README.md b/TelegramGPT/README.md index 1fb01f3f..01a7396e 100644 --- a/TelegramGPT/README.md +++ b/TelegramGPT/README.md @@ -2,7 +2,7 @@ Date Started: 28/05/2023 -Date Completed: - +Date Completed: 03/08/2023 This is a project that combines both the Telegram Chatbot API as well as OpenAI API. ~~These are the two-part guide for the Telegram Bot - [Create a Telegram Bot](https://khashtamov.com/en/how-to-create-a-telegram-bot-using-python/), [Deploy a Telegram Bot](https://khashtamov.com/en/how-to-deploy-telegram-bot-django/).~~ From 3b9bc121d4c99aa86a637b2b8c0af2d706d1b540 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Thu, 3 Aug 2023 12:41:37 +0800 Subject: [PATCH 039/117] Add .env content requirements --- TelegramGPT/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TelegramGPT/README.md b/TelegramGPT/README.md index 01a7396e..ec25e27f 100644 --- a/TelegramGPT/README.md +++ b/TelegramGPT/README.md @@ -6,6 +6,12 @@ Date Completed: 03/08/2023 This is a project that combines both the Telegram Chatbot API as well as OpenAI API. ~~These are the two-part guide for the Telegram Bot - [Create a Telegram Bot](https://khashtamov.com/en/how-to-create-a-telegram-bot-using-python/), [Deploy a Telegram Bot](https://khashtamov.com/en/how-to-deploy-telegram-bot-django/).~~ +NOTE: A `.env` file is required with the contents: +``` +TELEGRAM_TOKEN=... +OPENAI_API_KEY=... +``` + Dependencies: - python-telegram-bot 20.3 - openai 0.27.8 From 3d10454e32f6443914e52e00371f3cd8fafdfa22 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Tue, 8 Aug 2023 16:40:01 +0800 Subject: [PATCH 040/117] Individual .gitignore files per project --- IMDB_Movie_Scraper/.gitignore | 160 +++++++++++++++++++++++++++ .gitignore => TelegramGPT/.gitignore | 1 - 2 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 IMDB_Movie_Scraper/.gitignore rename .gitignore => TelegramGPT/.gitignore (99%) diff --git a/IMDB_Movie_Scraper/.gitignore b/IMDB_Movie_Scraper/.gitignore new file mode 100644 index 00000000..6769e21d --- /dev/null +++ b/IMDB_Movie_Scraper/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/.gitignore b/TelegramGPT/.gitignore similarity index 99% rename from .gitignore rename to TelegramGPT/.gitignore index 7493b832..a95fe14c 100644 --- a/.gitignore +++ b/TelegramGPT/.gitignore @@ -159,5 +159,4 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -# TelegramGPT chatbot.json \ No newline at end of file From 0f000e5627b6941a16c2bb16c66a9dba9531f086 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Tue, 8 Aug 2023 16:50:37 +0800 Subject: [PATCH 041/117] Init WPF template --- RPG_Game/.gitignore | 398 ++++++++++++++++++++++++++++++++++++ RPG_Game/App.xaml | 9 + RPG_Game/App.xaml.cs | 17 ++ RPG_Game/AssemblyInfo.cs | 10 + RPG_Game/MainWindow.xaml | 12 ++ RPG_Game/MainWindow.xaml.cs | 28 +++ RPG_Game/README.md | 13 ++ RPG_Game/RPG_Game.csproj | 10 + 8 files changed, 497 insertions(+) create mode 100644 RPG_Game/.gitignore create mode 100644 RPG_Game/App.xaml create mode 100644 RPG_Game/App.xaml.cs create mode 100644 RPG_Game/AssemblyInfo.cs create mode 100644 RPG_Game/MainWindow.xaml create mode 100644 RPG_Game/MainWindow.xaml.cs create mode 100644 RPG_Game/README.md create mode 100644 RPG_Game/RPG_Game.csproj diff --git a/RPG_Game/.gitignore b/RPG_Game/.gitignore new file mode 100644 index 00000000..8dd4607a --- /dev/null +++ b/RPG_Game/.gitignore @@ -0,0 +1,398 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml \ No newline at end of file diff --git a/RPG_Game/App.xaml b/RPG_Game/App.xaml new file mode 100644 index 00000000..b9691d2a --- /dev/null +++ b/RPG_Game/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/RPG_Game/App.xaml.cs b/RPG_Game/App.xaml.cs new file mode 100644 index 00000000..ecb54616 --- /dev/null +++ b/RPG_Game/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace RPG_Game +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/RPG_Game/AssemblyInfo.cs b/RPG_Game/AssemblyInfo.cs new file mode 100644 index 00000000..22112342 --- /dev/null +++ b/RPG_Game/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly:ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/RPG_Game/MainWindow.xaml b/RPG_Game/MainWindow.xaml new file mode 100644 index 00000000..b02a1a98 --- /dev/null +++ b/RPG_Game/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/RPG_Game/MainWindow.xaml.cs b/RPG_Game/MainWindow.xaml.cs new file mode 100644 index 00000000..5178cb6c --- /dev/null +++ b/RPG_Game/MainWindow.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace RPG_Game +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} diff --git a/RPG_Game/README.md b/RPG_Game/README.md new file mode 100644 index 00000000..9f0d6ae4 --- /dev/null +++ b/RPG_Game/README.md @@ -0,0 +1,13 @@ +# Learn C# By Building a Simple RPG Game + +Date Started: 08/08/2023 + +Date Completed: + +This is a mini-project to learn about C# and also to create my own RPG Game. Here is the [link](https://soscsrpg.com/) to the guide that I followed. + +Dependencies: +- dotnet 7.0.306 + +References: + \ No newline at end of file diff --git a/RPG_Game/RPG_Game.csproj b/RPG_Game/RPG_Game.csproj new file mode 100644 index 00000000..d84276cf --- /dev/null +++ b/RPG_Game/RPG_Game.csproj @@ -0,0 +1,10 @@ + + + + WinExe + net7.0-windows + enable + true + + + From 1921a8b5e5b9720ae8de103c51f32042813e8b06 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Tue, 8 Aug 2023 18:14:34 +0800 Subject: [PATCH 042/117] Add layout --- RPG_Game/MainWindow.xaml | 17 ++++++++++++++++- RPG_Game/README.md | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/RPG_Game/MainWindow.xaml b/RPG_Game/MainWindow.xaml index b02a1a98..f1a962af 100644 --- a/RPG_Game/MainWindow.xaml +++ b/RPG_Game/MainWindow.xaml @@ -5,8 +5,23 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:RPG_Game" mc:Ignorable="d" - Title="MainWindow" Height="450" Width="800"> + Title="RPG Game" Height="600" Width="800"> + + + + + + + + + + + + + + + diff --git a/RPG_Game/README.md b/RPG_Game/README.md index 9f0d6ae4..83d9d446 100644 --- a/RPG_Game/README.md +++ b/RPG_Game/README.md @@ -6,6 +6,8 @@ Date Completed: This is a mini-project to learn about C# and also to create my own RPG Game. Here is the [link](https://soscsrpg.com/) to the guide that I followed. +To run the app, run the command `dotnet run` in the `/RPG_Game` directory. + Dependencies: - dotnet 7.0.306 From dad08ee908b7f05d68760c99a87a132d2452b000 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Tue, 8 Aug 2023 18:26:59 +0800 Subject: [PATCH 043/117] Re-init Engine and UI folders --- RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj | 9 +++++++++ RPG_Game/{ => RPG_Game_UI}/App.xaml | 4 ++-- RPG_Game/{ => RPG_Game_UI}/App.xaml.cs | 2 +- RPG_Game/{ => RPG_Game_UI}/AssemblyInfo.cs | 0 RPG_Game/{ => RPG_Game_UI}/MainWindow.xaml | 4 ++-- RPG_Game/{ => RPG_Game_UI}/MainWindow.xaml.cs | 2 +- .../{RPG_Game.csproj => RPG_Game_UI/RPG_Game_UI.csproj} | 0 7 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj rename RPG_Game/{ => RPG_Game_UI}/App.xaml (74%) rename RPG_Game/{ => RPG_Game_UI}/App.xaml.cs (93%) rename RPG_Game/{ => RPG_Game_UI}/AssemblyInfo.cs (100%) rename RPG_Game/{ => RPG_Game_UI}/MainWindow.xaml (93%) rename RPG_Game/{ => RPG_Game_UI}/MainWindow.xaml.cs (96%) rename RPG_Game/{RPG_Game.csproj => RPG_Game_UI/RPG_Game_UI.csproj} (100%) diff --git a/RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj b/RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj new file mode 100644 index 00000000..1f1270fa --- /dev/null +++ b/RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj @@ -0,0 +1,9 @@ + + + + net7.0-windows + enable + true + + + diff --git a/RPG_Game/App.xaml b/RPG_Game/RPG_Game_UI/App.xaml similarity index 74% rename from RPG_Game/App.xaml rename to RPG_Game/RPG_Game_UI/App.xaml index b9691d2a..73f8ad7c 100644 --- a/RPG_Game/App.xaml +++ b/RPG_Game/RPG_Game_UI/App.xaml @@ -1,7 +1,7 @@ - diff --git a/RPG_Game/App.xaml.cs b/RPG_Game/RPG_Game_UI/App.xaml.cs similarity index 93% rename from RPG_Game/App.xaml.cs rename to RPG_Game/RPG_Game_UI/App.xaml.cs index ecb54616..f8390c83 100644 --- a/RPG_Game/App.xaml.cs +++ b/RPG_Game/RPG_Game_UI/App.xaml.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using System.Windows; -namespace RPG_Game +namespace RPG_Game_UI { /// /// Interaction logic for App.xaml diff --git a/RPG_Game/AssemblyInfo.cs b/RPG_Game/RPG_Game_UI/AssemblyInfo.cs similarity index 100% rename from RPG_Game/AssemblyInfo.cs rename to RPG_Game/RPG_Game_UI/AssemblyInfo.cs diff --git a/RPG_Game/MainWindow.xaml b/RPG_Game/RPG_Game_UI/MainWindow.xaml similarity index 93% rename from RPG_Game/MainWindow.xaml rename to RPG_Game/RPG_Game_UI/MainWindow.xaml index f1a962af..b968ef69 100644 --- a/RPG_Game/MainWindow.xaml +++ b/RPG_Game/RPG_Game_UI/MainWindow.xaml @@ -1,9 +1,9 @@ - diff --git a/RPG_Game/MainWindow.xaml.cs b/RPG_Game/RPG_Game_UI/MainWindow.xaml.cs similarity index 96% rename from RPG_Game/MainWindow.xaml.cs rename to RPG_Game/RPG_Game_UI/MainWindow.xaml.cs index 5178cb6c..28861ab1 100644 --- a/RPG_Game/MainWindow.xaml.cs +++ b/RPG_Game/RPG_Game_UI/MainWindow.xaml.cs @@ -13,7 +13,7 @@ using System.Windows.Navigation; using System.Windows.Shapes; -namespace RPG_Game +namespace RPG_Game_UI { /// /// Interaction logic for MainWindow.xaml diff --git a/RPG_Game/RPG_Game.csproj b/RPG_Game/RPG_Game_UI/RPG_Game_UI.csproj similarity index 100% rename from RPG_Game/RPG_Game.csproj rename to RPG_Game/RPG_Game_UI/RPG_Game_UI.csproj From 9a1063e6d414e2b0f4fd718bf2f817e5d8e3f3da Mon Sep 17 00:00:00 2001 From: thamruicong Date: Tue, 8 Aug 2023 19:20:29 +0800 Subject: [PATCH 044/117] Display player info --- RPG_Game/README.md | 2 +- .../Controllers/GameSession.cs | 19 ++++++++++ RPG_Game/RPG_Game_Engine/Models/Player.cs | 28 ++++++++++++++ RPG_Game/RPG_Game_UI/MainWindow.xaml | 37 +++++++++++++++++-- RPG_Game/RPG_Game_UI/MainWindow.xaml.cs | 5 +++ RPG_Game/RPG_Game_UI/RPG_Game_UI.csproj | 4 ++ 6 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs create mode 100644 RPG_Game/RPG_Game_Engine/Models/Player.cs diff --git a/RPG_Game/README.md b/RPG_Game/README.md index 83d9d446..e8c2489b 100644 --- a/RPG_Game/README.md +++ b/RPG_Game/README.md @@ -6,7 +6,7 @@ Date Completed: This is a mini-project to learn about C# and also to create my own RPG Game. Here is the [link](https://soscsrpg.com/) to the guide that I followed. -To run the app, run the command `dotnet run` in the `/RPG_Game` directory. +To run the app, run the command `dotnet run` in the `/RPG_Game_UI` directory. Dependencies: - dotnet 7.0.306 diff --git a/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs b/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs new file mode 100644 index 00000000..3b706d36 --- /dev/null +++ b/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Engine.Models; + +namespace Engine.Controllers +{ + public class GameSession + { + public Player CurrentPlayer { get; set; } + + public GameSession() + { + this.CurrentPlayer = new Player("Scott", "Fighter", 10, 0, 1, 100000); + } + } +} \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/Models/Player.cs b/RPG_Game/RPG_Game_Engine/Models/Player.cs new file mode 100644 index 00000000..a6cf7d5a --- /dev/null +++ b/RPG_Game/RPG_Game_Engine/Models/Player.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Engine.Models +{ + public class Player + { + public string Name { get; set; } + public string CharacterClass { get; set; } + public int HitPoints { get; set; } + public int ExperiencePoints { get; set; } + public int Level { get; set; } + public int Gold { get; set; } + + public Player(string name, string characterClass, int hitPoints, int experiencePoints, int level, int gold) + { + this.Name = name; + this.CharacterClass = characterClass; + this.HitPoints = hitPoints; + this.ExperiencePoints = experiencePoints; + this.Level = level; + this.Gold = gold; + } + } +} \ No newline at end of file diff --git a/RPG_Game/RPG_Game_UI/MainWindow.xaml b/RPG_Game/RPG_Game_UI/MainWindow.xaml index b968ef69..352df4ef 100644 --- a/RPG_Game/RPG_Game_UI/MainWindow.xaml +++ b/RPG_Game/RPG_Game_UI/MainWindow.xaml @@ -19,9 +19,38 @@ - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/RPG_Game/RPG_Game_UI/MainWindow.xaml.cs b/RPG_Game/RPG_Game_UI/MainWindow.xaml.cs index 28861ab1..e8b3b7b9 100644 --- a/RPG_Game/RPG_Game_UI/MainWindow.xaml.cs +++ b/RPG_Game/RPG_Game_UI/MainWindow.xaml.cs @@ -12,6 +12,7 @@ using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; +using Engine.Controllers; namespace RPG_Game_UI { @@ -20,9 +21,13 @@ namespace RPG_Game_UI /// public partial class MainWindow : Window { + private GameSession _gameSession; + public MainWindow() { InitializeComponent(); + _gameSession = new GameSession(); + DataContext = _gameSession; } } } diff --git a/RPG_Game/RPG_Game_UI/RPG_Game_UI.csproj b/RPG_Game/RPG_Game_UI/RPG_Game_UI.csproj index d84276cf..315e1e2f 100644 --- a/RPG_Game/RPG_Game_UI/RPG_Game_UI.csproj +++ b/RPG_Game/RPG_Game_UI/RPG_Game_UI.csproj @@ -7,4 +7,8 @@ true + + + + From 2eee776cce9fae7e69801be7e836a73533807b48 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Tue, 8 Aug 2023 23:52:17 +0800 Subject: [PATCH 045/117] Add locations --- .../Controllers/GameSession.cs | 2 + .../Images/Locations/FarmFields.png | Bin 0 -> 5405 bytes .../Images/Locations/Farmhouse.png | Bin 0 -> 710 bytes .../Images/Locations/HerbalistsGarden.png | Bin 0 -> 6753 bytes .../Images/Locations/HerbalistsHut.png | Bin 0 -> 2052 bytes .../RPG_Game_Engine/Images/Locations/Home.png | Bin 0 -> 1854 bytes .../Images/Locations/SpiderForest.png | Bin 0 -> 12236 bytes .../Images/Locations/TownGate.png | Bin 0 -> 1512 bytes .../Images/Locations/TownSquare.png | Bin 0 -> 2975 bytes .../Images/Locations/Trader.png | Bin 0 -> 919 bytes RPG_Game/RPG_Game_Engine/Models/Location.cs | 22 ++++ RPG_Game/RPG_Game_Engine/Models/Player.cs | 76 +++++++++++-- .../RPG_Game_Engine/RPG_Game_Engine.csproj | 12 +++ RPG_Game/RPG_Game_UI/MainWindow.xaml | 102 +++++++++++++----- RPG_Game/RPG_Game_UI/MainWindow.xaml.cs | 5 + 15 files changed, 183 insertions(+), 36 deletions(-) create mode 100644 RPG_Game/RPG_Game_Engine/Images/Locations/FarmFields.png create mode 100644 RPG_Game/RPG_Game_Engine/Images/Locations/Farmhouse.png create mode 100644 RPG_Game/RPG_Game_Engine/Images/Locations/HerbalistsGarden.png create mode 100644 RPG_Game/RPG_Game_Engine/Images/Locations/HerbalistsHut.png create mode 100644 RPG_Game/RPG_Game_Engine/Images/Locations/Home.png create mode 100644 RPG_Game/RPG_Game_Engine/Images/Locations/SpiderForest.png create mode 100644 RPG_Game/RPG_Game_Engine/Images/Locations/TownGate.png create mode 100644 RPG_Game/RPG_Game_Engine/Images/Locations/TownSquare.png create mode 100644 RPG_Game/RPG_Game_Engine/Images/Locations/Trader.png create mode 100644 RPG_Game/RPG_Game_Engine/Models/Location.cs diff --git a/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs b/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs index 3b706d36..1bf406b3 100644 --- a/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs +++ b/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs @@ -10,10 +10,12 @@ namespace Engine.Controllers public class GameSession { public Player CurrentPlayer { get; set; } + public Location CurrentLocation { get; set; } public GameSession() { this.CurrentPlayer = new Player("Scott", "Fighter", 10, 0, 1, 100000); + this.CurrentLocation = new Location("Home", "This is your house", "pack://application:,,,/RPG_Game_Engine;component/Images/Locations/Home.png"); } } } \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/Images/Locations/FarmFields.png b/RPG_Game/RPG_Game_Engine/Images/Locations/FarmFields.png new file mode 100644 index 0000000000000000000000000000000000000000..0413abbdadafbed20315f6921669249127d5f170 GIT binary patch literal 5405 zcmcIoXIB$Uu%`DS5{e+jLJ_1lsWD2CrifrbB%z4(1SAwgH9?RfqDUt+5k%=lIsp_S zASJXwFu*G!p&6tl^c(-c{dDh#-90;JX7|~dXZGw)g3WC+UM?{%1_lOR^IOKY^fB^Z z<6xuruVVaU=mU!<$Qs1JQ2UDe$d#2o=M1>z7{tK9*YmG23dvi<(1qs^CJu;u@W+Ty z*C!qf0j}OKgfh$nA+4#bs;s73$;H*hzyQcGHwM{-K{haLDMFcopgb1JZtHGcMP!(*soF|+f20ZK{XytuiYLoRvK zIzoQ>OThZwlS^c|b99Y6O2wq*Z6NDx|Ig78+N;6>s}S(g;iJpBO7lN(r;>~Ut3T%Gj zm|Y$ro?>*}&L*WgodT|#t)bfTg$WjSWNsF@gxWg9ua$cIY{x}^d1KSZbhp8j71O5B zFC5EENJW~gm-o}bmXNEAmljtt*X7cBT93MpW2Kru?c3C_vr?0k#k=3G-`<1tEw6p2 z4t4`0#4T@}<2J@~@dWHmxF}!NLom|?Kt4!j*s7Y1-bB1iQL>=H#>d|G__o+3a`iZT z!z}JCf)Fu(_@Mss+rT=kAeH0D2Shnj5*2aAL@_1h77Gt0w`0ih>;NtBk@Trfj%+Jc znk3PMj+Kde1TgE5!j(TQ5_h~Q=fQ)t$)T^sME42RmZljIqqdSepCKT+&373lAcD@Pe zHp!QBHYuG%NH!mZyS58eJ95I8AbGYsb0BLW_;assZrqVUkuH+ zF=a`Qr>gZ`xbR>onSc3i$4cgCi(ufNZTUm~JM~vHgBRO6FdsxOZ5fK3kqA$gX~}#1 zWjKAO&H0P>c*<3z_p)EcuO}b@6Q?Q&(h#H7c{bQ7>_R1}@10XovIz5boGl@53b8o$ zSQ9xId9{Bj&tUVw9nm*EFma`fU5VI9U+^K91E8CU`+Dx7E^`y@DHf5w_Qh$o>5nhx$5mhE2 z@*G>L#Nm3Bd3puB&l9EVFp|pB8kQW}{a4Q6$#lvq3j;vb%WJ{xBkh^*hYgQ*Uq{Gi z1};7$NZEJtw)FBXNDenxlEaUCEwMjuwj}cwdkvOz9E^?JzfxcW!Wl@6cN|G=YyLIO z;L$hjcQ{txJv-I{jU;stSft{=ABF|e!iCP5IE|IFY-;kep%1EOcbSw<5V}JrUasUs zS~%!lzRbk088&w32UA}G0JLF+c8<1b(~c@tyfnVYSI}sxT3y5#pMgN9aK!)>zBXk4 zNY_awUTx0lI}WMas`XZLS=_^|3~tzyrJxz_DBFr-_?wFk+5;DW*RurY2i#L;W`#!= z;V(HOv92fxoG3F;w$bBDvr%?O%M-V9n{XeK#CF`8LsP6rrI0}Y`{+&q()KEPV41iS za=7XnQc*gmtK}UAdn#62CCob@-c@&(FUYlG%^mbrO$F9eqrn>)ndX@I~f~Tv*oW@FZ1644vzUw-%%00N}4O2n2N$TB6@#T85CL= zl&r8Ef0Q8z%u(*NZxr}wZ-3cejaas-04Xhp*5%9QyeL+>3GIYB+ohWae>g!Lw!pEC zp$gGgiaWN1!&UXYqLfkPZ>TwSTl2%r`saqn%W-e(&OV#}G@aKE ztk-m8HsnOIU*+hQ+TLZK56cr9@v&~vM~`6sn+?0KxI~@K!2e6lt};o2%9Qai=AlX@ z3-l`jgCBl zH;TPwq`8^fug^S;;U8`bIJE6iukmgI0sLFmo*P?YBkqc5jhfeeDs->Rb*iwOVn14d zuX3#$ySlrbrbjrGQXTN%eY@e0m24qd7U{&-QA=r>oY3mm(FN&X{DQs6D6PF-9_~Bi z!pB^_UEo_dY`C7zJ{hCF(vf_X!0bGb!n)uTbwg`6O4}dS#h!%}HCB^9z(lA3|27wv z))PyNp6iisT?^Hsq=ZW{2gs+Iw~YLWedVJ%I8`r83AkkSLCbDrBW@$ zXg>`H2=G=y3cHiN?ET`;L93hJrT_BvwgH?qjwsXD-a&yeGNSU0r|2a-2GejB?aTW^}TTdVigQ32)P* zHdm&Ui~qUnvDXUkBd3Eg`4t!>^;iKlo$|oT-9gAZY zvGOKI-Q+m`Ml$vnkF!8XEbkju;j`0%3Ut@N~$`)ewcVq zq~S>kuXsWl2cR|Ns$TQl8A(98(roJdct7{K)-BuLvkJWB*2PO4Qi6rgzf{XoRZE8w z<^;1g+pf@A(6n%l3vtwYXy|>H{Ed{b&{7iX-dbL*-ygS9guJ!1asRk5haOtIumr*m z<;@&@929(T0ec>!`rsgOBxv%R6Bq@V4avB|t0yVyK3sL2m%}vsK1@kESd;?3bZh}O zK#G>ZettIi5o#U%UMDXw`+=A>m!7(7jU$>}!m)x681wqXaRiczB$2Z>V4I! zY{w@A+wI=CPR-bZ>JQc$b6&R!*7NfpnOTOiNuZ1Il$1LPX}6MoGZfn<7p}pk%F|KR zE~*^HYic25HBZ>FT!I}Ma1yEtCzz?SUJUV`K0pvBHSaYNC#u9iD!?M-YH%i7M|d3s zl`ow8?{pag@MK$&l1v#);cn0Uxw|=r~BD1>{?~ow^uBj!eTMtxJ8Y!6FG9ZC#x1DSKUR8KaI&bW=@JGLiA zW#If8yx29~M0w(vT3K7G^!n#)?G9$b%jNn~-oaA6fGl`!`m8?nG5w9B*NAa^hFsFL zsAHU0rmQ_q=;D6oMtgdo9tFa=aWYXFuB3i>)yP`(sqZmAg!9Vs{VTs{G3a2cAdL3! z25zK;q~Ri}B>0gt=oMDR^X2C(!rd8P&uwdQ?aP)3=Q*btUu%C0M{GfkU7rrwLBlLb zqcof$D!=o)zxdgH?pr?sRuA%3Wof&<{fxvGPbSQ?Gdl6F{RaL&b8X)HKW&GdHDk~a zB{ktQox8vdRLT`WWio!~4+sYxXRFC!)o%?MXEViWKxS&UJctJ1EzuIOXy|lo;&Xn9 z!E~O|sf_{4TJIkU59aUTdOb+gcTV{wikUlw=D9ied93`Md7TI_PA$#Vv1v@fpbQobcnJ7tf}XV-181Ph%oaJsQ^!^ zR;l`nVGDtEy8GcszjhZ^_*de9=BeQYY84A%t*;bt$3KZ%!4}`mX&%x9w*E)&>SuY- z(Z&uu&r^oQ;QA;KHps)@C&&4ja?X8p95(+4FF1GX+qejgmDDa*F?iEVkMRA~Z@HPg z=DG1ABfIaQQ=?PA2Rpa7aIedpM?084`Cn>k_6+Yz;g_|NC=o>b8t+aKzP9%6*FH91 zK~F12#i6fV=kCg95Kij`g|e-NS~HB)NOs!S-Px$J(7fElSV^p{nlL(gX*}GM=g)1~ zSVO;KK&X^;QNQ7=5C(mP>4|6H(boXZ+;ZQYmfAQNvF22H-%XI^id~kYH0Y+nVhC1` z-29lHd%<@s=e*53L@?I7b%P;$F|)c~q!T}fIiF$T6+e3}Z3-vR^+y;{&6o^cdUgm2 zcbIuU_1{z?exR07b0__9GN;q7ZzBrLcKqK^!dHtOh0@CXyuE=qglgFzd)#L_Dws&D zrmSI4)GEm{%}5Ko+q{3EUB5MS_?+e$YQV^(=bhYJZ0%w&_xs`)k`al>-1U(g}nXo3??$;$tMk?N?{91V_==nw($%h!EeG&mT3?Pl9$Q%kDs&o1sf?#NEX}dz0v<@Ebj{2p$GZ zEAMNL*4s5*59b;fHc8OC?O{Pi!Tja#7yB)QP&?5u2Y8>rY%!O5Z zJcy8C%5B*$!v{t5&u`@5^{4L9JJV}bj81u7)8cxT#dEZ+l8kxl zwsC*oVFZWcKM*;er~NXwn$35m#_|N#N%gnj=?Q?8!O(mM)@y7w+h>5tGc0$%Uv3RI z8y%7;mnQpvtC;uvBph2Hp*EOx^>Ey$)emB(>sZ8sEp+eXYx_l6TKg>s=ol&#FHwji>T@I$d1^7KvK0y(Y|a*sdp zHoIAMTsmkxpVL?^jXcm>b{z!V$=!+fJ4vH8RT(1)A2=%6L5DfWGpb&(@aaPL>)u3E z%v&GYC2Mg@4gE+{A=!(UoXGGqfJ~$qxT@7ILjO{I^~V`iJ!i7 z_h6${kap=y-$ciCb&jK&Vo%~x*U0m3k*v>F7ZbBt3X~URRa4tS(@x=?Ojq+3B&vYG zkKL8HtAZ@F;-w2OIkQ553sAMes&E3g=C^%rBv!1$p2x~bR)8K^pyLhXo7vmBoWia( zGez}Islh%OoHu%!S|0rDRdCKyd*)BA0yDlodI5gxO}%m2W72!|aGuZz0%#<_R&*+Z zPlbOxI*1R_UEsA$6WwG6KF*fz5`h*iIB8s|9pk6DSHaVgKZt-;CDr^vF)E#`s=NO% z8)L^wzrVm-h}xdB7njzWe3#fF{5u(So&k~P?@@bv)8sfddlgE`JVRIe(bDgx<{t1l)`NwZgCkqe#_gi28nex&joCnYiR#-?X=>yyn4bN2K zX!A*`furNnn8NtVu+$V4jJD=-ec8R^|uOAse*}YBk_BwqJUfkbb2GmPXO4j}tU&bBL zUGt@CnN8kt-SzL?$0$|a`fj<~eTLrMAcOK7j>Rtgz4D^D!{c=q{$6<@y`V7sMP1ex z-U~axwz83;eTmH7Z}*pibxipmd#Vj&v??gbXdO+Pf4`Nsl~A=a^VlN=OmhsLu6{1- HoD!M^;?wB_cthrfJn16Qqm!?q=0lT-6^>=i!8l#w}5m>2uMhGilmeal1tZ;3j)&M zv+vJ;@LbpThq>pPbIm>TIx***nG>s}p+ta3jfa7OL7<{6uY>Lb|7*{mp})x?F0AMd z3oHYY!N90b#=p11MvrkJ%Eq1;7%%((YnbHhsv&416;#0ps_SkK^|AV3hXJv2bc6D` z*+E|k@$&P&;jh8N>qF03Rgss`^R+n6_k)q|v=3`uMntd@T?VfC80%rl>tQL7HHF3L zdtj19hE>EcGBV;y;NCH@bLP6#;61l)j>C6CAXc3bh@GH=W{Uvn2bh26DyMAF)lGMr z%C35W=Ld~c(ml~&Shi=ul^+{;_e$zSN)97r%IC%G-j=IgNph=pqSUG)@Ya{+|B%qn zbsmy#_yp05L-yiXfeWrnE|W^!32Tzmg@;EMhVNUttbB~OsqDkP?+s}AMdVV+|EbZU zw`VE`Xa72zeHC`Dj7UXo7H`8xBws(){-ABX5SInsw!N1}z(LoG@^6`?aTj2XZ5%gw z+xZv`3Xt4*_Ad0=5Qr2%+ZXlqhRQ$<&X4o8ve)3H_^Q--Tm|nq4WVA~cfecG1;H7T z{3p@|QzY5_64HDlB!;ZDqy~p>a8F*1rYc1SoICojF;Gva-nsYPqn@*e{+YN3zJ?Kz zL1BjNpmbCPdPXfDQ1WVe(L=UN%;34k_W6PKX61G>hq$9WZ{Q^m*0{&7n&018Gk-Qf zG;0KSdn^3w%5FV}`3$7e`{}v`!aq5oGSsd_0Zu>CbHi!%;jFQUf*;M;o^StsR{n&=-&?Cnkmz=hoylpvXky$ z<{|vp>a4;pme^EMQ7603v8R%xudNP&CH00$mVDy2vEQzTsAnmisa;Zn=S)jouuuWI z)k#nRB;70LPJK}w;dG`zL4IF`m9U3{xS|_^Uwjhu^EFOvmR^OB`}S>iVRH#Q){9G`?w8M-F% zJuNuqK6bLasVtSC%(7%y$sE{f2vfhQywe*T88pt~=fZCUUuM>JXqtbfwgE+EVc=s_ z-DFIu#Qi9bgq1I}?7h{w9_yFCOC=0_3)rLOraDQ{p>-?Oa2JtSHX9U zbO0j5GlD0q$Vhx0QB{u>I35FAH3xf(e*8k@mnRg}s(#41+Pz5W8Aj2G!M4oYOf<~W)L@fjY6||TT5O#3uJ{IvcRF7A;dOA^eeJ_R|$kiIfG+|J)6$l zBdyBdKVI#iZ#o?lrt07~4sT#K_(N>4u zv1?l#Yz4S65nI`fVle5~xw!rd_~{(o_IIr~@;Bv*_fx@O?gHecXQmFxq4H}~Q~ZAxrxeseLzXXVVM!Z=HdI2@@rkk+TCUES zl{zALWbV#1NBiVmVVM)X;e zXBMxI6ms1x+DCKPa$ou@p~N`6uJuS`-WPJtpTITJud6_JxLZT-Od{YS*52r%FP=2v z2Dp`?mu9QBaMiB0n8Cy+TnVR{yLtldD3EBRma}sJ1LD|NCi11p;*%ynuBAGit(@N* zq4|oSdm}8k;Y1_Svu6kS@FR&=!@q4t?)wIvJH7kn-s~})O@y3w*pk`RPLnu1EWsQQ zv^zP`L}y3^zf%i%xDM(j1^=Lq2$s&Vxb~5|NFixAo*r{sAaa<-q7)@EB7Q5=ACCmR zC-2?rPM2}qsKdvn`p71^=~9)c;r}Tyd=c7q9%+49V9Ki5L7a~bUK=(YtiDVAa~P0& zAepmv90(s){hO8bd|=F1gm+T>HH*oLoM!4vSOpvr6qHi5%Bl@LB8@{6mmNaxW^Hd; zbMMx$I=Ud3H7$Wj<9`d8>{e>wzJ81y_QOGP+*Us~ zwx#x%wB6h*U(Py_xCe)>w-tak*kMi1TdeLoMxcb>BuDZcGr+xJ=Oo)Nb-Y@MKK8f`&u#{?MOQ-{q9o-mbs@;1=fpkRG(tuT{H|vijGL86pC7o4 zag$CyYr_aM$jC5Kaqk((3I2Sn%2nfra`^msFYqF z|KmF@qjLpdMLvl%;Et@_D}GjmAoAhL_c(9Tt9%|`mHmPO0+dIEmS6t~cqO|ZSu`=IR;>Z@6tGpK%Dd!D8X}h>@cT@v@W<;Lsf}Red zMP@U#IPrSPkEhykd0#`+dSig3>INXiPLYN(G^`j}3c2fBR!cb&GR7`C%V3roe{Z-J z(P;y2qYpFsw`G>LMxQGU@eP1`U>NLB_@8hVYE_|`8PSU=nac}P_+8K4l{BIXAkP})vvzM%d zJy7P`ZJvz1pQ)Y0 z9TdO_Rm?VwhV9>DZO04PP=8FyGRY*CtQt6zKZ|LFtX1pbERX2Fii)8a-&2|O7DNbb zAdEI~uXEMXR0@>{6;GGdu0!+EJJ{>@)y1fqOV7s0`}j$YNUOnA+sA2Tw}s`OgxvDX zw<{906rDh~puI1-KaV#)F969OdH~-$mt3KI4;1_yQ@`I>R>;mKbrbHSu&}g^t}G+h~f=)x##Po zD!;tw6Xtp8&z~_-uy1km83WH}DN}hqyh(tVLINi8&JP;HTWOq&&mH0?d32jRTJc;H zE=EnGsZCjaXY9>}I&Ty9z9n969abO{26Y)&0HRCsa_lh6PiB-*BJncnNL#JsFR^<~ zuW{*w%ZsNDZUJ^t8o(&q1hW_(QOBj?>Db?LU(mQ-^FMXwTEeZ(ou@iV3qcL;J6t+C|Go=U z6Ot~7Jrt4e8Et6MN@R3|%RZ(jM`&C0_OObUZBJk#N@AN6nVpn9rYK^ohbPeg+rk zvbCv~0p-nhDzM7N^onnlijz~iT52PrP;(OgT#=&_Z^m_>TY||;+Ng`{01FI=aCl z{&YZu*UIT;Gz~fex{b2?g{jZ$UKQ*B%sDPoQqYf zlV;%Aj1(RI?yZ@xZwHUXX6n#?&Z`eMR$J`%o`{v3u^|8jj90dA9mq#RFfWMTASD5P ze3MwGd?z?suva0E?FJ^D6Qd4)R6E3$9;pKdd!v7|bh+0rDSxGw>&MMbc+p63AS?T} z2Nd(evQgmFQsEXWvHRv@w=u%rUElg*nN^NL;>?EQmiueOh<4+=%oQ$nF0(0-BHN(G zO)>d&(EO8QVbLK`70M+>Ry)(Q!DFRwl^wL{&|{V00Uc%UMuL9 z-hWLOJL{Y&cS$lbv~j$_tL2HHV!KJ!BySL?+pP+LnM`1!aY6EUroliQ#z#a1PgNEi!uN0BW#-L z$SgH3iNco9hd%;7rpjEvPwD9YG39*Eyu;-*+6_>}1Fx|=ph9K-t-)KCe-Wo_#qIgd60vyz@lycMA{M_CY zF$Pca8BMdeLX|mm_A-ip;@7O}otT-zfm~;KHMwwOcaFu#ub!6Z(dA9Zm&TlQW7(9gC)!USN9c_YVtY$KJhpYEl0N@opMe)EDDgF9G zDlQX15~lqHmfKf%T=h36=>iuQ%_NH?@Ga8N?1zw1ftdH7XyDjE_Bd018vTbGC*E<2 z>BtsKj1#abK8*qWI+`eA8)dFwW)jv`{5rRXiZrddE9O`$Wr2!el7S|(x->-wb!~>r zi*5$Zi3_90#u;rpSnS1v6MsXBJ43*3x74IFQ)-_wH#1nR!|RJyQjBO0V3nNstFe&lG!0IR8`Zk739vj1mqw%p_b9 z=Se1{+8~>_rDvPS`ny<=;@m1z{7`PJd>4o>B%MS2dz40>tgqi3lY5b_FHrzy^QgR5 zqDx*`&*`@2?dKQZ#+RaqA5f$LU*v$^MOZuGD^~Dy#n1)6v^fk89gD909?@0r`_HHR zO~Bm)rNABMi$W3FctT00Fhc&|aO`GH4eHmt`5|Epu&5j6(7VzHOL5~#Gw&eYd>!bV zyc=94IgCu`mYwT}1!M>37Hx6&?n>d{r#<({=$~$XC8Z3Z$e_x7>Wi$wkov?(I|Wy& z^a~&;DXSQ&Tu&?L@&hhPja%ieq%hyp?t9wl3h=;3^J~;RPD*ttMV;9@_*yKq_~mYK z%-RpSSqK6>XVe2mR9?l6IOU%2qdYq{V&q8k_;tkS)V??G8-aur-{LC{^Ef&@D)^K> zeoY*$0{wh9mekDwGps649y&%w#;OyuS`UAdFkr^7n2Q8J1$ zZK#y$&SU9vyve>din!~AzlxUBcrW_m)yOY1!2 z+L()#HvJ;%AK&NQ{wSU*>9vw7ank>GCu*wuez0&%!?Vkyx1;Zw7YjvI)oM%(nADh& zWhzUa>+*k)Q@Y4Jk9PX!h$DNeZATdF0D2+88!==xw*|B*)hfa^J*@ zc6t%rOljrnwymM#<6pXP;sI_}+sFs>?kWGBQu8h_PQ*oDxJPVB1xi2HFQ#l>#$$P# zUqUk zWt(AknYw~bi$}IVOp`@4RXct-)u1%sPTUR*s3M#A7|nW_RRi==!y;`To94e9ces%9 z;i0|<1BCpzX2!pp&ZoYQa1LU5`Zs8X)^)OFxKz?GXQF8N$%L$JtfjVS$F7>^C!4== zoYU2N_TTM5`uYDrOk|aBXki)SC|0GrVkx$~;+<-Gd?JJyZ~fUwWM;VZ3M;RFkHdQV zwS==kQ_}{@LdvsucLKa!WtXdc%knG`_-y56(>qHJO0wuQb>_KNrLie-#>3if_*L%D z^BuF#o9eHt(9hV^MZ>i?)&~q_%y%`zrwfmWiCnHlOKXq`&oP=h=)4#;1}bV>Sr>G4#+7+HC1Q>;*s#!S>XO%p74yBSJ53TH$+s zDCz{>e9qmULG|x|2H0yw{gnyYWcxPCfTwrO478PlOx8}}A$M!oZ%%e2o9#ZZc0Qvn*U$d iHU2+~C3ZyD!RIDuLJV@S!vB_xHoQv-|8kzuDR6xAW{S!w-Yf zfa$>?2-5IEBLfsZ{U1|TR-Cs^RO=|*=4e-6R|pcYwys91K#(%z=Y#i9;PJ;cW!gXl z59R2SM)C0$NKD-E;1usOCo@uz(M}aR@(pyd4{GHY>NAZEicG#{Sr~Gx3^~@{lpHoK zvo$NTv$zO9RN-`(i?Ct4*mK?CmF^Cg6pD1J@^s>PB6uj*%Se|RZxk2nS%>wk_d_=X zcsJm%SAzYULvj3X@mIqGTOz{iV}n~FL#{=Iwh|-Sq7!N(gAcAK;9Rn9x8)19{FI&I5E zXyiNZ$amRU;I^CLzK7wiRp_b9MC&op`YfM)Y|H@;*02<3T#7R(#ha8JH>(J-uKf1M zr3g5W=u|^;;U=OQPNAE~K3A#u_Dq70 zfIve}WZAWLjjpc$PbGPi4iUWjon9%_naM$gV1O+5$ z@CXZr{K4Zu@FWZjhXV=qy*Pb#gan>bz<9>z%OZ!WMw;OKcb$1LJ`~?A z;ReA-LG5s3?bD`)5q{$+|H@cP>v%`6wEy;0zqls^x9d z2&$`xxZ+9Xllyry59x0QMH4eEV03o;#oSMGALm!)V0C$IeSIAOplHAUt@P`7)k2UG zLsM4?3{orDr2V-qNr z2`4H0j#ye-9{@dd+zN;A74d!?sqW@?I_l<_7R zlQNksw?H-oY(%|FMCv*C+c3JrWt(XS$M0uFYcev8dY+46hU+|R+e)0s70ihzlOyME zTT{bzg_i$@IW&2=j{hgloctz2`q~o}yqB^%Xp#EvJ)Mi*?!|Duw;a}Ay6E?TwP514 zIC?_;^s=VBn5kFbj40`4mOs~oGUX3rk{}|Vzo?k^=6Cfy(JFu) zmaYT`idM^e2>w$E2SiTqyprEbBScZrin1+qXAe)z8$F%36CbaN3(Wq}FLY_-Wuv}9 z)rY2d_2;dq5W&h_m!N&7gHhXszG0_BADy`Fk$RN=Jk3qGeH%rkKF3_16eUfmX`jki zds7k6M|x0G-Yip7y}ilbRXa63l=T&SsD~tpMP}Y45L#kj>^Br&U z;1oLfW2R#etDU94>H|WPK40)%g{(&u#H;1Q;*57cA4^RSUeqSqNbbDhk!WLgY-5i_ zuZLzp|4E{`GH)V{aqi2snr@K>AjfB zNU(vW0SE*Fqfvf19jE?|o!fOW_v;G>b+jcBhx7#r`}YAlv5n>v-~$3Z%r#s)udma) z5>OF|AdvC%-?2p+U;CvlxtECuXX4|j%;fWzDWHV&u?(gYgTl0Rae_G=hBX=(JOzQa z3(F%+&lma{-GZBnO^D6 zcOvJnztO9@e(KqE6)e%jXEfXLxS!bJ)uVYb>q`HnQ;uZ%C!V~k+BFS&@O`ZFve)?7 z*y|b(Kf4JdBmH=oX=itkW;?vw>iQ*v|3?Gie%v@WTa1ilB{gf$Kpo@MPO2~z(Nkvlbu_#^~X3iev z$`>&R+YCz@r*jNvkYReGeIbuX0`}@2MU#7$%P-%}xs*RCwl;U*?}*s-pg7!Oczt-P zi`4WgY_j`k_A!gozC{8+;X)ifxO^HP$=2peIh*Mx8vAPtMd5Eel3H(+sUB9zmfo%_ zofVl|9qFAZ({jD%sG0pnW2(C|dol!M(~|zRp1rP`P*+W2WIbGsk+L#93Jk3@x7+Bf z&BeQb%)IjBtwXv=={w~#?Ra(SbR23%>)-2Bw-pheyoiy(We6E^k5_P4*L*;)W68Og zJ6tbf0#K5cJ@me2LnI5J%D2HwYnA|CtQgEWh6V=Fz$FwW7mj1?%U4X^=2~agEVZg`Z;OF!=Wu`pb!P%|Cqw#GP1Z{`hR-< zHGRn`)juLkJ>cB2sP(U5V!sGxrQd?bnx#1IgO28ha~Iqvf?^Mo3^OSJLH1dl)#Xsv zLqVhI-WGm4;_s*xcODz79g}NUZAlPaN1yl=8ao$xhC-l*X^$gHWAFX?spM{lsSw#i z1*#*A`AWAI$?M9FyT2Y*`E!M2x60<`Eu3RDEe*34DF?wV12J^{Sy*PR z*zOKPzrefvaP9)_+X{l@98=eYLTI}wDjuC7n%zNFIfu8`~7MY@&EceZ^Sx_x?3y6`{Dlky6GTEne8*_(41h;j&rCz>S{ zyi=&QvVoMv-APYyX5@ekj)w=4oqxv6rmAU>eB({?S*>{zXYSF@VWRdZndq&8ZPOa` z#xvJ)Z+W!K(*e#u$KT(7{21C2$=j{kSkBx<`-rPs2{&H(%IiZNF14OnF@)`s&_9%a z{%I4Ew<7)cub>FbMr5ueJwFOKQOAltFfz7AyYx^s^V7YH`q<5p%Kc=w7GK_?x>OTg z{^dmStc1BNj-^MXaO8S$D~Z$!Q2q?(j=U`n)M#y=dyww+IJ)xLX93b#iMTRjc8(r1 zzCfh*5$bw|;$}ym@+z=wMQbp;=VY5_2;FX=^_m(#^wE{w9O?vruq=~R3Bm9$s;vqUtL z@4=+foUAy>MOX`EVwF`RBz8~@8f$CsDHOO5DJQuF1^hzmMhF-j92f{`k8+xwpD*i; z4oF9V`=+l^0Idzpw>jZ75?NPYfAh%Eqfl?}iV?8ob_~0%#-`%snVE3WA zPS-=1p6afyicnFK{)|kB{Nclg&$2QSYX7j`e}IVaZ;ktDPyG+US&1r&e)v!yhw@?q z|8I}vB%|y4;RD*hfAA3pAouehkpL*E15|gk1bUdbSbT6Yv2g%0IamP6Iha_OSXru( zk^4S;V3w7Y5Y_PferEW|Q)3ucNm&Lap9&5pV9EzeEUgAkg%7|eQwxI>Koz`KB627x z5=j}3yfhWI9}o$SjMVABQ;C$rD{773^M~ZFceZ!7_vdXRIYXY`9Pj@AV7~^Z^I9ED z7~*|UdKom%IF#59wJE>~Au5Sb!glyY!>8J2^8Y~GK=VAVoD5&V4I|)J+nahJqP=$8PyxClMuL$fE1U0)uXFHznS1-046qvsGbB8=2L{^gY{ndq6ZSLmeqhpL zg@?|OH|gR9*f>tW`Fz-Ij?f9-z>oTL$%4DZ+UffK<=bFVxELWme@-VsUB1WK8VToj z$5_i;chQNsJHa)x-0IOJBZnsW=SCLA7k*Z^XVixx_+`Y!esqCedcqpp5Y}3SgM4|h zJFz)QE4aKKzD-oYOF)hq>DAH8lejiG z{;ML(fd(<@>#;L%Wy&y{S+JLlu-Z08l0WTw>PGi5;GZTD=b5B+NiHNcdP;g{INP2N ztDb9VyapKOfpW-IFZD9e==Ic>LeR(1NjQ( z)>zzxc3h|;E$35+7*oDqd5Z&uc<~dUG1{%#K1CFILZB~}=Q8S}@G(KnqleDii^p=r%kXT*n#9$}Q(p9=lcC^oPJIexdkwBh#5uA=gf=Hq}AU}N4!AH^_#CxQT5<-lzNIu+*FRzo+3 z!3^wHiW>53PLzf$cMyG;UVjXDyy<7mvcEFtd^kb$z6lA?rzBUdNB0-an=lJbqs5IO zK-hJrEZe7KNuF+-E=A`oLFz&grA0W6Qit5^H4@WFm&qKQvPl!184zPPT*K)Oeg8Tq zjpr#c@;!*7!vGKIU2W`{{bQvg*|yHZdtlaBsr}vo*CXdq|V&Zs&Tz@2yFK z7hL8^M^}d^VOOf`#EjskD8SDTZHM+J}Wecy2VPtR}kTgz&m>48~bXiCBp2G zenby`96Co@t_tR?29*`#x0WM8BJU;SBFw5@S-wu|*QLuYf-Bda%VRDXUmx7iZUHoQ zcdAPLs{!DIsA@e9kPUwh`mMh<-^pvHr=IO*?S{N?MGP1{lxbD1Fgm{39r>(%YtfB|%q8X#!gb|_*ey!o>vu31|UKP2>; z==e^+IK#7Y>LaWw9Gj&+L+r)xib%rV=Ha<++23{oy_$KiP=5JPq;dcn$+q2!ESKw5 zrP=VMTbXk{tUv=vQC7Ue(phi5lVM7s36=xhu&SfDd^E@b6U|64G~FhG$K#fGgjP7Z z$pccT;Ubx^u~Ttx+uFlG*%}>sFF#{OoQh!XD-@UeMFWl~w>HRfty}q4B7uWI5A|s7 z!AF>1rhz%mS)?XdA8GR&1?Q>?xmfJFvR;#OinDuyvOK&)(q<@YM!ybVK)S~2OJ7dk zX%|Mzb$MX%)x<4jx-uf}n4_$jlM!CrUf%kjH-KLk#_X0{IoE-(e1|&$ z*w6??3lfDG;E*8@HyDe`YtgzeoCL{{K3(&4`%Xn_RhuR0|0AAK53KKHbBaoOUA#t? zT92+6VKre!(1L$+60^lGj;p~fNXDUji{I|~J4Gd4rn3dL(%&ChRhknn$JY1=IVKou z&etYaNDI^9h|NKjV25=wtx)Z7-NK?P6&O~(!dzXIQAR$HI;S~A1U|_NPR`QD2+)(9 z`%Zw?%elXGnGc8}4vYJ9-HTSwQNz98?IX73numOBbo}kB2S4^?70Ny8bXhIY)i~S5 zLql|T?-+ZFTq{5AhjTq7>SlVf>*DRmK;08hAE4jaN8&$1HHcQ>iV^DSV>nG2Edr_f zI_emugs8We3HE8NRKn3-RcW;4W$;h+KljbzEEcfpDs$f~DUIw#qt-=5O*C3;mze^z zi6|b13@_FhaPAv5SW-ys_RCQKbXLJ@_Y$H$3gLdT^iH|xlm~HqU_0gCezo%OGQ-Cq z`D%mcgm>2KY^m)*#e`>2wj%20}MvAs~%$cJCN^$&Y{4o!1> zW!J=7Ff-z+-rp@WalbmFx`VMB)-92@BXDP2vpqh*hJ9&d+BnZ6@8dlr)Fs*L(~}(o z;ZXKssL5<;mM_@_%EQR5k|vmA`pJOEk^|qFj*>yIZzwJ=2|(w$nPQe2H79UzffHww z=a+=|_z=!yMx|mR^3L`8-wgDkGe|&Li2Nq-%jTR zdl1(@*BNUoO`n^ngqUFOT~pAVN@^&DcIH7kZnm%kX)Ti3IEvdT2FN(YqN(Xdn(3ZY z;W4`4&P-w-S?D&k#cf^ca`t{pVh)*SQF7l;yigRzL^s$|G-Mb9UqJf23R+c)1Wa+J zrHbKUnwm*2QcIMEYnLfE6Hd)vt6;j$#S|>kg=a%$IqIk)_6KW90#=ZdRg?#S99DZb z8u@eQjYV#iRCEcAnFgK@%?a_7kT7=jG&5}+48m$X($R63^$gRdJglE;$GUF&rX#;) z+60AoC>_{U6Wyt;h0o|M#2eAjP}*%bC2dZ8m9`^Krh>W`m1<4?)R7B@OqMy-fUvmW z6&=t9JZQ~=Wc~~g3}cOv+bHaGVHR5^!G;4IY1XtRz}khV&iKbi7Pz#?P;IFupdaC` zFTfAH<^^eBiFHAT^#Jb0Ouj`vz*R;;xHBi!SeV2h4E+5xE4#%`&sd9(db|prjD%mC zS=+Llm$^!4ivA8|E<+1v-f-`r#ZBzB+EmKg)RsNJq*OZm6LSt32|^d-h6xcN$z{?F zeijU`;@B$pjK!)uXSSZ!@8&=QKVefL1?-^ijt1;Nh^FN7BU@~g1kt~vd?pehmx2X& zp1tVd$k#!ty!ptKIp;Cx)fo*u(f8=XmTBVE7K(E$qTPY=Gu>QY%oALllNs{KS@!gj zB}~I}Dv=jqXNn!u6!d7BmNLrS*yA-c%SXo2BAIXH1>JgfVx^q)DPuxb>eh&U8Q`Xj z;Hj<~y1Ta+=HFU<`tzWpXqR`MTckMhScw;3L|uN;Z2mcoo!M+EG~u{w-ADGy`Ok^= z42qvg_BKD|DBE6J7sF#KeNwVoPgp?8xJk%vgV}fd7f7(kvpJenNzCvx%IJmD6}CX< zy=3yDZP|6~J)lms8{eFlxg!XAX>0xBVkhu=h9E&B7)&d)N-nOYKJGQ>XAm6WS&>G# zV_m~q2idiV{g(k%WP}>=714mA5z2N0*Tv|b-%x_Vr^dmIHBrCLkd}OpjSzWM^krMB zca}dCb=F>N7|N{DwVWF8w%|tMoaGV++ZOt%05go37*jD9UoK-0Yi@^`t-hF*aAD~# z>rQbfV${fco62E+@3}z;jN=&dVji`pcP2w>uUx{Cg*BwRShQoh^8x!bfItPkz1b14DGQ5ZU^}yEglNQgFNi#cQ^sEBkTs4*S!f*8AS5O*WC*&cE0v6wRtmtY35kXuBXP|UuOS&M${rph zPVIE(^KM5E{e3(aCSI-Km?Q^vR={W}``|h??ZgYaG(WEic11M1cwVa&wSdSQnX%UB7 zkv?VSn_=myz6HjUCrPtP{L{IChMwvso1bjbkF=)qT6i^MFjmTTk22v;w~vGMc0Vxfe(Joc%PMTDA4>wrpfqAQCcBWFe*Dy9UE9Bkp*eLi#-UO5%euxW zxXyd* z;Djk;7&UCEdLSm5KAWq~)(vCJnRXP3{b2&f>jm zq|6Bb3p#I8b=Y(#uREqOe@e~)hY0@3z;Tzr!45U0hiM?{W9(!zfH%-EaoVKB5nL58 zLuDEzmVt15J2QOk8v4lkW-T~Hw7c3?@Cv&DU@vW8GsE9$@{GEo3AVD7hX==~qeb0t zo7D-XVF_jJvqx_pcipxUD1fg)m5TpvI~|$Y=2k{D%U9czm0Q+VlX@B+_|D5foT^pZ9Xe(RAY5}1Ezct6rOE5TDsV$nUnpZeSJD& zrFuw4NnAxK|t1Vy;9S~flT;|$or0>68l)KQOFf8Z%dfY`}_B589*cUgZ z^H-nNB6`tl;zHW9=!@1D+vuVG?2GXPzi>RH(gmSezJ((kL6=E5)B$Xw?V7Z~tv7M_ zw(8bH?5xLv6C8&12ef}7{o|la7D>zd!!25d50!y}Ftdhq7)kg1tG5t-Zks3O5A}0R zJz+d6{2Qz3r@SPY7>k&c^kI%wFN*F%pg-`$2u^R6++Lpt{@O}k>kPqKTm-tEs0Ys( zGk(IrR8!~yiS*?PtWmDK^&ygW58j7=N*XoEdEpRtl<2NPl&3xkW|%1!1GHxriE!id zKwk+OAWYV>6l5>zqLf}sUTncXzcjyTnj_*nzAA)H#61j-YAjO zq3$l8uOwTEnH(uHs>^+RPmU!JQ)$2r#!nT6gcW^|MN#VP91!Y#Ax#o48U(^;X7pv$ z%Q;7Cwc|yhi3GlKYd3%}Kj*A8Fl!;Db-%ee4q)JMZL*1`ysgH0^d zA1cxY-JXB795Ng`LF+LHe_(O$7X!8lKDV>*; z4zqz;s2y9mk9lw>VkgrO#X^}|G{LGk%-It+=xK78sAqNW6*h+~G}+^zV0%o<9dfSc z#$6*b9Ea6dKRfebZkaTg5SlDCuPr|1p-mN9^hN!imI^$y%mA5_Kt8i!W-NWSobt!Z z#wnQKd|mSFdEgLU+Z@Dlo;1kgydD{+oR6|(knK&*O8ho<0u9d+6gzuLf9^MRTP5q~ zQ2jKSP0pcJbi6k+%E+QWGlb2aD7#;M*RJ&IYF43Xa~d}16V5Qes^xlj$%i1S(%18=l5&bM-w+V+x^#P=_b>L0E&b|}xha{FAV-DM@zh-w)Q%Kxm zGrZk$wOLHc_fpTd>r=)S^382x!X3Z3F`bTQY$mWN3n}?PSMPBLI0!6>HyO8f5buvPbyxas1n|=I(5^=2aBF1HJi^>PkcgLF(Q|$J>!-i@sOqF}FL^HGvvg=Mh6QZ04ui<7jEZW8TJIkfurmqpKzq z^x@@SG9~1xN}e1U!rY*lO-=uiEz!1XZQ?8l6&0PyR^Ucp6{)GCc?w};q zY6Nc+hv&p&5-QMYVMV@23S^eCq;fDQcFT6)mKh1=1NYm+(Brrb;2P*cR|7~6Aq$T305N@JIJx@5%XDe4ZKRR1Pq z#b&Ou=B|j29M@Hn+%7WVoE7G{F{Eoggq4JzGCM#4rT%Vbk}$cbyb618QZwM&VZ#Z)oA6Ko#D+zg~XwZ3t8%Ik6TwVd^ysWkE@*qI5UiVgTUys$^84$no- zRSe~p*-J453?TvVOf=@eEUcQ2r5U>YEy2PvJ*Va81J_@CZPa)EPh1B&P0P$77xS2l z3BQO?T~cVbtIdlev=@^Hf;yX0%i6nG*Aii-sM=|Eq&C7{!-k1xzF{%k|Mw^wrvO;X ze~uPl{j9uAO*~-Y2KwV?;$CE@xK@9hZ;9XSRK+K(Kor8+xmbSdCyi%;kft59MS&ZW z2KaeSe8uAd=6xw%c-n1N`$S#%dU3H{)0%UWUJ#{jQg~H@HN5Lki`Ex$x@m7{Yv3Z# z^5VfTFk`z4ZmFh2S|lq*C(vqupMza%l*s&B6NtG;M1I=oZ4Fql346;a6WHKRBT82G zRkf&z)?NDCOmdJGR76RNuY;_YRgA z`qtVza=qH4;2f+0=e=H|m9gQ6@j|0QP{uFa0v}J(hEqnA_9g6WFlzr_`Of;f&R?oh zGe{I~!^%0LC+*pD6$W)$)Og#-S)NbHARq#Nt{fBOf;Y{vZQ*M^pd*T{w0Ac(UIv^ahm`F30|7vF-`FAoD0Bdc};aoCm>7%5Vk_oEiv?n^!q4u+Wr0o83{fOVu#e zBH?&1=Oo33x9$K)oJcim4kcM9f{SNu&7zETXgw4R%$IY2_=5hFgAP|#)i55b;DrA; zPNXwf{it-*fGIb;k30!wZm9|4T4%dE8EUkPec}*3B1QE!^vtdayxY7L9ZRN>nfb$I z43@5~EH*=P^#;wqhQ2UB{=|*-&361A+8W7mj*D0F_Z@7miZcXP*L7F+$wI?L<;+S` z9Zpy1E-e}|3odJX>UYi zkyj`;ltQdBw(Z|vivJ5AF4Q1ryr*?=?L%7+ez>V=&r>dH2=oXU@r4O)!{FBSm-^P! zqJ8*%HAQx#9fdW`r_@yG;Yk$EEyfE`MbGcGp>=lEs_)r%$G)w)-N~=IdX-g{LD2*j zbtK>vt=CYXbMHRT0@V#Iq)`q->Km0}LcNC!Pt$M2EP)FWHT7@o z*6OJWlCAfj@_s9`4e1K%Vk_wg1dJ7@^e?$PxCf6$4RG$-Gz$x*c~oGaI-tO+xM@0b ztZHD!7y&~5x<(bqMl*L6o1845;u#!%Uaid>{immqGfPdpmxZSE#pB3fChvDIr?nu8 zi*^H8YUTy$Eq~kmyO9IikDC*UqiAzdWNCJX*aW_(^Nh~R&|>rcMHqy8JeB*xSEFa+oO zR@*$a0&li>&5$=N^_86+w$XBOAmv26&U;i3u9tR@7=p8ebJFQxu5c&I7{Nptu=^ZI zKv!a};KPl2cpO{dYWqtc2`vwfn`FQ6e4o8U4vag$qB$>)<0U2HVxy|}sv=)QF#8uL zqBRf*s-+gF{Pem6wQ~fI-vNr&j80E%n>Ymlr)t&d&Sc4~8 z&v9n&7j@5U?s2G9Eew&Ui^&}IBEzx$=dfjqe>iG^M$bk-^>1z3h>PR-W^(d)YrR!i zfofuNmY)`;7vDh$yG7a*m+*3Y7=9Eu^KmT!@AvbpFJW}}>{c)}=7yS;+IWr36ju?` z;XHgxYM#473?wrobojCBw_3^(X+CSX$s&WadW9z5jGBPg70Syu)1)T=U8#pc3iU0{ zwojaYB{5YQN30@ojG{b^h@Htae7LV%N9NEPt&sMPIHu7=n>WEq(e_e-q}a(SF+_>9 zK6BelIX*eV+>oR(Rr=GUx=mlt^LY8?uU(gmGz(SJAP##pE3`#d>e`t^?~BBaE#KbO zKlnICvrJ*`}g}aF1~?*RDTFscFUG@{&-%?w4Nag1*phkTyv4nv-O{ zep14fN*CGf>Jk8f@s(~0p&mm&(z7Z+;lMus~SE{D%^~-}R9_!0l>}iEXu+`Wv zVHu)|F1cG@Ow_ril#w4KXo6skpWGf7hdCSuq^W#6H7Rm4Kl!Vmq22}W8p-*m*J+Cd z+8$pzP2{9!WgL&3YgW7CZVB0zzwEAbtP{?(vutu!(}Dzjc+wiTADSZYpH|w42R^44 zSON5%3iCJGv`5O^zj)_oTkqn`4!FcPHyP>|af?uwe@HYG7$M`7_-e(FOJn98Ci+ok zB_Xta#nYh8efkbmBk$;I#*4YKKM}evLCuj2aUyw$eiN`g5eB^=H05|~&w-zbAy+A0 z_rsU9^B@5j zjX7wJ$o@6USn$^oTvjsS#`Nn#{M+vqoG~0LDVM+4wAAEWyl7ykAHi@l@v}OZIH+f9 zQ@_dCGRIN}`Yfm-Fx=p;-zzOWxZFIg-EqZbnscVZ3LC`VS$WP--u}hi{`=1e_R;d! zHFgepp*B_HQDtbJ8IY%B%?LT-%C`qXL)G0d)JP=+sHab3I0kFe3Jt+|X)(92(ZIy>al#E_Bp_V!;(}a7NT$}lt%DuyA& z{lO`vz@Y^LNwY~6AX_C{!IqaifkNgEPo6hKUW-b~SX*)RzTAi=FI`j>WtPE_d6ABm zSW&$l%q*)062szq8SR1B_j5VBzG}D zh>^@e3(9LzEY&S~1wd|3Z(K#R0l#jS%x{iNQJOU>sC<_i2d0|}4eR4IHQM)?8KIN# z8&+PW=o;GK66PxMXgUd&ztiVQiT_^P=4)GcS7X0|j=_Qwj&1qhQF(`!~s`QT~-j%~c$o zFm(T;0RqncfpT?AUo{`)HTG|CNVSu7lasSA1?$yVWB~WWS3%b#rL?@0MHeonLucv9 zr)*p=vd#{n0_KaSUIlM;X2~{OPF(F;gJ7D2qsywzsgsa?6(Rr8YO{uAsn7G1`n}ig zIzSSp48T=2yXV#FZKUMYBJt8sX(`TLifxH1K*MzANWPq&MKf;x>CF$K6mr$L_LQQ| z3x4a^+}k#c=qPgM>esrv>4;xEW*mQI7ANL;R#vHUFqOmJERSwk#YO<5VvRrb)cz`= zAv^{CczNQzC8dwXp?Rvfj9GlwoJqT_`S?S@-`t?v)Q4>J=5-NmyGA<%DA%yd*NUNF z738T`lg~ZXIM%dX+L}FkbKVbMdb(A)Qt{p0`{amo6|jQ`6u& z(53*b10@|-t#9ks2vh4hB3ZFX4e1&_(jatjnVr@ihxaS>$J$p9BEsTIur;NFv#@KU z#qVxY2n3K2&1_*HhDR{V>n00%KZ`2-F*apHZFqtmIk>pHn1ke!MdRm6OtgXl(X zFc~8sS7eBPRKWmK2`sz3*JHf@AK*#0S|`^q}YCZ8+%q^QY5jx?eTBnV4~ zqK?t+C2%ZXcueEnSJe6bWYF}zJWi5JonN}4!KK95rk0G~-+-<*c7IZ`1?lp|7qR=t z%trGjvy*<3&|M0hEsZQQTu{A=d%T$Ix4F3Xn`L)X-RMaBXu)S8+~6mn5oMpmZj`1r2#@_uha0YK`;p$x5^;t z?)C0#hmGKTs=+G=#29HQ6WD*GEbX0X8hP;}s-Vs*g5F$`V#v^Rw(E`HFon>=-;J2B zNgHD_4~wi?ad@y+SzYY}3tHRbF$=8Y_AR1yO}3YwA$a)AkK_)`nr-o{X~+mzd}A!a zy4j2NwTW&>@KW$Fbr>+~pHs`nzN>lstPwKh7(?jWczA`+UcsnDy6jjf;$7f-aV=d| z8M4Z?im}#@u|5NGm&4eo4RP@bl;iI0sMcsjS|0`s5;EV>~~=hLm6cL4GCd9?3+70A}KM(1vH*Z9LY>jo{Q(OKy_!%gEkOY_Mp7NyavHcS@v;Zsgf^`vWy6g_2)D0MSjm!gwts zfroBt`l%gcL*D5Gjf9X*(r%F`dWe=v?_2%4RA?_u_RT}=8$P-&R^`U`$hU|urjs?a zPTDRE!I4EPmczk14gvtd;V&3|T~p>|t$GIZ7`t(pGo9^|6S|ZOsL?zY51FB;_|mOUL>#Ebc9MZt}=>x1cZzYn!tWFc9{%lqxOV>?&@Y9DR+pIIR`8 zQ9@XAmObLKfjHuWOjr`}I2f&>dF&Xk8C7RC1mBPp+OE}uG3*f`*ddoH6 zAq$^*lpbYE_iTNZ<(50$t|0v9*`b%6I2o{ATJexL_1$I*egBP+^o8IPKME%OyJ5xl zXk5$cXKHEX_sH+pWGa=Bq_K%ih61I5yd@{e{v*q#o9owXSH2NYKhM6r- zi0E&4DvJ(su=YNP%i^tBr&)l`X&tDKQ24_{h^Ekb&tKtWFV&qdmV|EPwQ?7l^buM_ zWlD`U?}}MlWT}s-en=KVv}|Ab5J1vWf$RLYY}4P0OI6}XbLh36!PSs+&l%oEDOwSV z4E_&Nat7L_P~T1Fwv;0Ed!3a+WK3_)p+@FO@yztI>6c5iV^|o=zhtK?;zLTMn?G}e zy^)-o>&~)9j*SQpCH}-DJgtRdbmhQp&VuFJB~vg^ezL~7?Lh5kLN4&I5KNbeC0B$f zUWg)&Du&{gJU2;;u1RWi=9*y$qZx~Z_DV}qNPd?HcS#5CcQ&n=a!j+dibRyAcbG0) z--6}mw5#58GfuL*Kq|qVSAttWP9h95`f3HpBYy^v(?h7MbC-=S8}lej=kboh(B6$8 zmv>hRGuj->{zKNU{9Hc6KEQmkgBhvp3sGhAm!C(Szze;agxSXV2Q*UlYzWj|Hm0GAasR}vV6&eQ%34e!UKZ)W~l~#|f>lWDPtSDH$ahmF2(aZ13wKw*i`zRJ* zJ%fa)pzc_)^e{#@#A3cyU&Jubxj1+ab(rsUdWcDRK*QX`rO^jDGsyH}oEykO(F;yp z9gxk@3kXP(JyVC)zVA^9ezDK;%{8zE`HS}b3&J*43jsk^n3V^LEq7yv?P#!kldLId zq%K}(l#?$e~LFzYgVkY zwMgo}iPpVC-%^g)9i@3>xwk@7rc1EzdM=3{yc{}E^dowiF10c5Q`$sGC>^MgBX2y& zl-d8u<;bYb@2;4uiq4`q!R(+xP(Im}w{E2Ux-Zo*EX^bz-otbW#&op^^x0C(_36yd zlVL7{ylDQHx46Gaw0&wRi3!9dDqah!PW<~&AyF&0mH}?V+M-(jNrWTjhs)0fqs_GU zavRKPiT7SVl;zImcYYSS8dCLdpxPAVJUC{(9hI%j9L)6<5_G@tkSOuu^0oJ`+0)wU zXQi3X|E#LRB{0{|>?0&qVWChOoOA)ECZ(^>@bCRUEVM)22F0OHSxfJX)V`%_*MUPX zqu!w5jhpn=uUSPz7o#cLk?kaM+eYIW7C_8_BWzuX-gl*dBp@uZzQrCq?GXZxZ0o3S z;Eg|WR?$1`Hq|Q`BJGjV+9iu3u)t}MV>)8s^Q;6dGPeR&Hel`lFOhxgO1N(n8S?W7 TtIfa3`wy~`N)k0<#)1C>Yx(4p literal 0 HcmV?d00001 diff --git a/RPG_Game/RPG_Game_Engine/Images/Locations/TownGate.png b/RPG_Game/RPG_Game_Engine/Images/Locations/TownGate.png new file mode 100644 index 0000000000000000000000000000000000000000..90a599d144f7cebae38e64a65a4256cc7509602e GIT binary patch literal 1512 zcmeAS@N?(olHy`uVBq!ia0vp^wIIyF3?z5N%@GGuEa{HEjtmSN`?>!lvNA9*GX(gA zxB|uNje&@<-i)o@oW0(hqu!FM-jb)@hQHoHtlmef-cPUI-=IF&%)!B-KH0xMC9pm% zv_3PkzPh}=rn13=slk-F*^0B>UZB%iq}xrR+g-B9U8>Dfp({YEp`oF%v9YzawWFg0 zXwfJb4S~TH0@m_9d50E{-7?&Tp^X_G@+!X-)JG znb;+Au`2KD-}@5}ZE&0E%q#wGP28IIT{+*L@N+88`RcQ3^NMqeth!(6U}4AyvKz#( zrQ7%I+vnG;O<(UsUJsR>JmZz{Rqd$V?#pJdI9f{2!plLJ5;@NW?Z`~uDAR^Q>N@%9-S5MR>~E{UN!7o zX%{Fv;nabqSMy#e!kogmk_V_-|DxBQ_y7OMv^D=d{_Q2x-iwdF{e1eh_QSWIf47!v zTRh$wd7yOpFF^|~3B6`xxn&#eUq1^;51i{-Dt0vzh0z-M{x`E{?%PM@(@x9+hCYL* LtDnm{r-UW|`iS-* literal 0 HcmV?d00001 diff --git a/RPG_Game/RPG_Game_Engine/Images/Locations/TownSquare.png b/RPG_Game/RPG_Game_Engine/Images/Locations/TownSquare.png new file mode 100644 index 0000000000000000000000000000000000000000..c780585cc616cd2d7d73e11ccd0f40eb9f45c9ea GIT binary patch literal 2975 zcma);XHXN`632r`jl@Pm5rm6$>56D*hJZ8$1Vnm|5<=*bCoxooOOXzjqEzWdKtmCb za(Of(CR8z@h#(0aBqS0*Uz~X#-@LgW_UxSfot-^%_WWly$;Qf5NKjl5000P?n;F}3 zJnpyg^K#bom;gDBJANN_3kCo*r2`MWcsP53U^AyM008v&Z{q?hTEuXIV&Nu^;dVjy z!ZBW<2tcqGGB8{%5D_kYSxrMtQv)w3*aH9nCCrUs_WyF$xsi0h}`TYdwZ@h>2rSit*o_`jRQUe0)Ξk$OUP}3?u{nK zX+hJ{Di#$O6%~tgzCml^o}o_BvuB6qfmuaZrqgGssA2tISkg5t{sGe&yA`%6DJv7p z#S7+cDf8~RVt-;fA(@M6qv?50nKuC5AA_zDZ4~++5G0#`bNO{ff4D+DCE$^ZM<(kv zFPQI4tHfltar&FpY8feg`PciBvoc%vg_J?EysdPP@T37ugq}Ht^(Moghju*NnJJE}Wb> zhO!^@+%DwJG^O%Jl#Ju##T{RimCI>trJeln{(uOHVMX(izBof5GBs;z#=s@2;J`JHlV?+5IHU6YgWRnDc;m~+KCetxOXa5ZD&d3B z)V9<(0Z}M7fNIj7(U5{FrP&1^`WhkcrjClV>G{M#77=Ps;D`8Tg+f;vD!%HI! zTDL>#n75OiYnSOhCniJrz^bnoKgqx^x8V8| zDnIiYKd?%f_uOVn&=espR>EJm%IEB!zRHj%BPxEi??K_R0ZKsGnTJ@DAMdl$Y(?A9 z@%J392pTVqhOOz}_+duN|5TB8ANO|8-)2;+W>S0Ma*G1Kx1QkUr<5WF6!4 zpV;ZZg_V?czH2w8%E}QL15ND-E268{szS|Zj(1HVH4~9((BiF%QK!erc_FB^{6a5n zkR|@)YIxvdUP;!dCK>LWKJVVl3s&8I3Y2k->px;N_$!+Vnzj!cj098i=p*#%<>x*%tfz-+6Z@;!3yTo+^o$eM_Bqc7*nA zXjE}<+y^11?7Z}$;F1<{OtxTq+g{8?%TC^^Py&q^9L^r?e0u&;J*Pv|)}U(GMjGCw zb#?PF-|0y*q(B=55%S069lyRJ0sbB|NLynzF2Iq(dQ11&i)kB-vEIM%J%6O1e^fkM(g zs}r^M>0z6$j7m)8D-~G3O33Pj(zW0(F%uILu4SFfjMOlJ zImpwq#bAWni**y!r!7c-oopoX7>;mBgHuMDdva^v5Lk^Mcwceg;jz-o;Mt8f68!Z3#{TJF>eJdF@r?^S?@e(;Q?#97w=n z@_P~bL#*I>!jX9iL+3Z7LZI}cJDjKbNHr!=?L2kXt=TWS%e*z6IwJ6?N<}W@NP{Y0 zc_D`~QJqTrxjVlMBR7jR*hO1IUATh@p&6Yc9&n1eot@p%(CNv+TA#@G9ju?Gf$D}+ zTduqKX!0(NIBVT4=C+Q!xlrR6aEg(XuBze}#1fl!V*jY4dt0;1MQxgJG63N`JtcJ$UA zw`0-hmHtL3;_o>$wWW4{M&U?+(+aC#t0X&jcQw(4Ru6!PFJo?g=u1Mh-Ti7FN_>RL z1`2qY@|3B7c7Aj3mT>r^Znc#}@9X`Suh;cmFgNGxcX}IIX@B>wB?5t1nz*gq(Bi>Q zslOq4n4KDMSnoPdW~~Tw5BBeboX7A9(M)y8=Lx_KxUSQInD8fj*gzV2vK*x zDVeXWVHK<2r6+4{A{F!IOHFZF8m_N$Pf|Cc#%X5CnLH(8JkgROCv)^r>rVDcX|g z8`P$pXM~HtFVYdQku_4JW16moSV%iZ`LiYP^J|lBKg`&9snYWFFQ(WpW>Z$;UwExIQ^t}K$CeB%b=gPH4}=@_?Q!*lc8d}i;|g3Y&!4 pZ*xlgM?>QOogn`iOg+IiM3y$^yl*Dva)xPuxrvqWDk|cXQTHoK9+?!%&qRZ$bV94@qeq^`uE<}%<%pZ=e8cmZ~wS% zkLEL>edgU4=kxU${))fZBX{oLi{BD^-Foa;7cZCCtKw#V;Du?@7fmtQzb8JOuV9=W zzyAV%fUw*AMXN6A@2g!Zzd#MDwB_Rq{Ezh++i zKk?{`hrZnpWghzWysEfuP{r9ZFX@X|(ib5K+oLaZkGxQgiLDpYe^B+k)8?Mp&7OTH zj<@Wpb;;Nx Roe#`J44$rjF6*2UngDeIv5o)$ literal 0 HcmV?d00001 diff --git a/RPG_Game/RPG_Game_Engine/Models/Location.cs b/RPG_Game/RPG_Game_Engine/Models/Location.cs new file mode 100644 index 00000000..64efbe94 --- /dev/null +++ b/RPG_Game/RPG_Game_Engine/Models/Location.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Engine.Models +{ + public class Location + { + public string Name { get; set; } + public string Description { get; set; } + public string ImageName { get; set; } + + public Location(string name, string description, string imageName) + { + this.Name = name; + this.Description = description; + this.ImageName = imageName; + } + } +} \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/Models/Player.cs b/RPG_Game/RPG_Game_Engine/Models/Player.cs index a6cf7d5a..8c905b66 100644 --- a/RPG_Game/RPG_Game_Engine/Models/Player.cs +++ b/RPG_Game/RPG_Game_Engine/Models/Player.cs @@ -3,17 +3,73 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.ComponentModel; namespace Engine.Models { - public class Player + public class Player : INotifyPropertyChanged { - public string Name { get; set; } - public string CharacterClass { get; set; } - public int HitPoints { get; set; } - public int ExperiencePoints { get; set; } - public int Level { get; set; } - public int Gold { get; set; } + private string _name; + private string _characterClass; + private int _hitPoints; + private int _experiencePoints; + private int _level; + private int _gold; + + public string Name + { + get { return _name; } + set + { + _name = value; + OnPropertyChanged(nameof(Name)); + } + } + public string CharacterClass + { + get { return _characterClass; } + set + { + _characterClass = value; + OnPropertyChanged(nameof(CharacterClass)); + } + } + public int HitPoints + { + get { return _hitPoints; } + set + { + _hitPoints = value; + OnPropertyChanged(nameof(HitPoints)); + } + } + public int ExperiencePoints + { + get { return _experiencePoints; } + set + { + _experiencePoints = value; + OnPropertyChanged(nameof(ExperiencePoints)); + } + } + public int Level + { + get { return _level; } + set + { + _level = value; + OnPropertyChanged(nameof(Level)); + } + } + public int Gold + { + get { return _gold; } + set + { + _gold = value; + OnPropertyChanged(nameof(Gold)); + } + } public Player(string name, string characterClass, int hitPoints, int experiencePoints, int level, int gold) { @@ -24,5 +80,11 @@ public Player(string name, string characterClass, int hitPoints, int experienceP this.Level = level; this.Gold = gold; } + + public event PropertyChangedEventHandler PropertyChanged; + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } } } \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj b/RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj index 1f1270fa..056bf455 100644 --- a/RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj +++ b/RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj @@ -6,4 +6,16 @@ true + + + + + + + + + + + + diff --git a/RPG_Game/RPG_Game_UI/MainWindow.xaml b/RPG_Game/RPG_Game_UI/MainWindow.xaml index 352df4ef..282d6300 100644 --- a/RPG_Game/RPG_Game_UI/MainWindow.xaml +++ b/RPG_Game/RPG_Game_UI/MainWindow.xaml @@ -5,12 +5,11 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:RPG_Game_UI" mc:Ignorable="d" - Title="RPG Game" Height="600" Width="800"> + Title="RPG Game" Height="700" Width="800" ResizeMode="CanMinimize"> - @@ -20,37 +19,82 @@ - + - - - - - - + + - - - - - - + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RPG_Game/RPG_Game_UI/MainWindow.xaml.cs b/RPG_Game/RPG_Game_UI/MainWindow.xaml.cs index e8b3b7b9..6ee92a34 100644 --- a/RPG_Game/RPG_Game_UI/MainWindow.xaml.cs +++ b/RPG_Game/RPG_Game_UI/MainWindow.xaml.cs @@ -29,5 +29,10 @@ public MainWindow() _gameSession = new GameSession(); DataContext = _gameSession; } + + private void ButtonBase_OnClick(object sender, RoutedEventArgs e) + { + _gameSession.CurrentPlayer.ExperiencePoints += 10; + } } } From 71c384df2c216b245d04e5533f30983e2f6dc2a9 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Tue, 8 Aug 2023 23:59:28 +0800 Subject: [PATCH 046/117] Update README.md --- RPG_Game/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RPG_Game/README.md b/RPG_Game/README.md index e8c2489b..9795bd36 100644 --- a/RPG_Game/README.md +++ b/RPG_Game/README.md @@ -6,7 +6,11 @@ Date Completed: This is a mini-project to learn about C# and also to create my own RPG Game. Here is the [link](https://soscsrpg.com/) to the guide that I followed. -To run the app, run the command `dotnet run` in the `/RPG_Game_UI` directory. +To launch the app: +- cd to the `/RPG_Game_UI` directory +- run the command `dotnet build` to build the application +- then `dotnet run` to launch + Dependencies: - dotnet 7.0.306 From 6f18d9b40a75d2702947ec937bf381117787e43f Mon Sep 17 00:00:00 2001 From: thamruicong Date: Wed, 9 Aug 2023 15:07:36 +0800 Subject: [PATCH 047/117] Movement/Shop controls --- .vscode/settings.json | 3 + RPG_Game/RPG_Game.sln | 31 +++++++++++ RPG_Game/RPG_Game_Engine/BaseNotification.cs | 17 ++++++ .../Controllers/GameSession.cs | 29 ++++++++-- .../RPG_Game_Engine/Factories/WorldFactory.cs | 47 ++++++++++++++++ RPG_Game/RPG_Game_Engine/Models/Location.cs | 1 - RPG_Game/RPG_Game_Engine/Models/Player.cs | 10 +--- RPG_Game/RPG_Game_Engine/Models/World.cs | 43 +++++++++++++++ RPG_Game/RPG_Game_UI/MainWindow.xaml | 55 ++++++++++--------- RPG_Game/RPG_Game_UI/MainWindow.xaml.cs | 9 ++- 10 files changed, 203 insertions(+), 42 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 RPG_Game/RPG_Game.sln create mode 100644 RPG_Game/RPG_Game_Engine/BaseNotification.cs create mode 100644 RPG_Game/RPG_Game_Engine/Factories/WorldFactory.cs create mode 100644 RPG_Game/RPG_Game_Engine/Models/World.cs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..7e50f7c9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dotnet.defaultSolution": "RPG_Game/RPG_Game.sln" +} \ No newline at end of file diff --git a/RPG_Game/RPG_Game.sln b/RPG_Game/RPG_Game.sln new file mode 100644 index 00000000..056c9163 --- /dev/null +++ b/RPG_Game/RPG_Game.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPG_Game_Engine", "RPG_Game_Engine\RPG_Game_Engine.csproj", "{462D9413-3AB3-46A9-A7FF-CCFD5B124E0E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPG_Game_UI", "RPG_Game_UI\RPG_Game_UI.csproj", "{D03C6C5C-EB70-440A-84FE-7F5A69AA5097}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {462D9413-3AB3-46A9-A7FF-CCFD5B124E0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {462D9413-3AB3-46A9-A7FF-CCFD5B124E0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {462D9413-3AB3-46A9-A7FF-CCFD5B124E0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {462D9413-3AB3-46A9-A7FF-CCFD5B124E0E}.Release|Any CPU.Build.0 = Release|Any CPU + {D03C6C5C-EB70-440A-84FE-7F5A69AA5097}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D03C6C5C-EB70-440A-84FE-7F5A69AA5097}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D03C6C5C-EB70-440A-84FE-7F5A69AA5097}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D03C6C5C-EB70-440A-84FE-7F5A69AA5097}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A464F9CF-ECB4-439D-AB13-55C112AEFB61} + EndGlobalSection +EndGlobal diff --git a/RPG_Game/RPG_Game_Engine/BaseNotification.cs b/RPG_Game/RPG_Game_Engine/BaseNotification.cs new file mode 100644 index 00000000..14472f18 --- /dev/null +++ b/RPG_Game/RPG_Game_Engine/BaseNotification.cs @@ -0,0 +1,17 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.ComponentModel; + +namespace Engine +{ + public class BaseNotification : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs b/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs index 1bf406b3..d83d86bd 100644 --- a/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs +++ b/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs @@ -1,21 +1,42 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Engine.Models; +using Engine.Factories; namespace Engine.Controllers { - public class GameSession + public class GameSession : BaseNotification { + private Location _currentLocation; public Player CurrentPlayer { get; set; } - public Location CurrentLocation { get; set; } + public Location CurrentLocation + { + get { return _currentLocation; } + set + { + _currentLocation = value; + OnPropertyChanged(nameof(CurrentLocation)); + } + } + public World CurrentWorld { get; set; } public GameSession() { this.CurrentPlayer = new Player("Scott", "Fighter", 10, 0, 1, 100000); - this.CurrentLocation = new Location("Home", "This is your house", "pack://application:,,,/RPG_Game_Engine;component/Images/Locations/Home.png"); + this.CurrentWorld = new WorldFactory().CreateWorld(); + this.CurrentLocation = this.CurrentWorld.GetNewLocation(this.CurrentLocation); + } + + public void OnClick_Move() + { + this.CurrentLocation = this.CurrentWorld.GetNewLocation(this.CurrentLocation); + } + + public void OnClick_Shop() + { + this.CurrentLocation = this.CurrentWorld.GetLocation("Shop"); } } } \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/Factories/WorldFactory.cs b/RPG_Game/RPG_Game_Engine/Factories/WorldFactory.cs new file mode 100644 index 00000000..e2a14b57 --- /dev/null +++ b/RPG_Game/RPG_Game_Engine/Factories/WorldFactory.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Engine.Models; + +namespace Engine.Factories +{ + internal class WorldFactory + { + internal World CreateWorld() + { + World newWorld = new World(); + + newWorld.AddLocation("Farmer's Field", + "There are rows of corn growing here, with giant rats hiding between them.", + "pack://application:,,,/RPG_Game_Engine;component/Images/Locations/FarmFields.png"); + newWorld.AddLocation("Farmer's House", + "This is the house of your neighbor, Farmer Ted.", + "pack://application:,,,/RPG_Game_Engine;component/Images/Locations/Farmhouse.png"); + newWorld.AddLocation("Home", + "This is your home", + "pack://application:,,,/RPG_Game_Engine;component/Images/Locations/Home.png"); + newWorld.AddLocation("Town square", + "You see a fountain here.", + "pack://application:,,,/RPG_Game_Engine;component/Images/Locations/TownSquare.png"); + newWorld.AddLocation("Town Gate", + "There is a gate here, protecting the town from giant spiders.", + "pack://application:,,,/RPG_Game_Engine;component/Images/Locations/TownGate.png"); + newWorld.AddLocation("Spider Forest", + "The trees in this forest are covered with spider webs.", + "pack://application:,,,/RPG_Game_Engine;component/Images/Locations/SpiderForest.png"); + newWorld.AddLocation("Herbalist's hut", + "You see a small hut, with plants drying from the roof.", + "pack://application:,,,/RPG_Game_Engine;component/Images/Locations/HerbalistsHut.png"); + newWorld.AddLocation("Herbalist's garden", + "There are many plants here, with snakes hiding behind them.", + "pack://application:,,,/RPG_Game_Engine;component/Images/Locations/HerbalistsGarden.png"); + + newWorld.AddLocation("Shop", "Trading Shop", + "The shop of Susan, the trader.", + "pack://application:,,,/RPG_Game_Engine;component/Images/Locations/Trader.png"); + + return newWorld; + } + } +} \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/Models/Location.cs b/RPG_Game/RPG_Game_Engine/Models/Location.cs index 64efbe94..e096350f 100644 --- a/RPG_Game/RPG_Game_Engine/Models/Location.cs +++ b/RPG_Game/RPG_Game_Engine/Models/Location.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; diff --git a/RPG_Game/RPG_Game_Engine/Models/Player.cs b/RPG_Game/RPG_Game_Engine/Models/Player.cs index 8c905b66..378f0e8f 100644 --- a/RPG_Game/RPG_Game_Engine/Models/Player.cs +++ b/RPG_Game/RPG_Game_Engine/Models/Player.cs @@ -1,13 +1,11 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using System.ComponentModel; namespace Engine.Models { - public class Player : INotifyPropertyChanged + public class Player : BaseNotification { private string _name; private string _characterClass; @@ -80,11 +78,5 @@ public Player(string name, string characterClass, int hitPoints, int experienceP this.Level = level; this.Gold = gold; } - - public event PropertyChangedEventHandler PropertyChanged; - protected void OnPropertyChanged(string propertyName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } } } \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/Models/World.cs b/RPG_Game/RPG_Game_Engine/Models/World.cs new file mode 100644 index 00000000..fb2b36de --- /dev/null +++ b/RPG_Game/RPG_Game_Engine/Models/World.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace Engine.Models +{ + public class World + { + private List _locations = new List(); + private Dictionary _special_locations = new Dictionary(); + public World() {} + + internal void AddLocation(string name, string description, string imageName) + { + _locations.Add(new Location(name, description, imageName)); + } + + internal void AddLocation(string key, string name, string description, string imageName) + { + _special_locations.Add(key, new Location(name, description, imageName)); + } + + internal Location GetNewLocation(Location prev) + { + while (true) + { + Random random = new Random(); + int index = random.Next(0, _locations.Count); + if (_locations[index] != prev) + { + return _locations[index]; + } + } + } + + internal Location GetLocation(string key) + { + return _special_locations[key]; + } + } +} \ No newline at end of file diff --git a/RPG_Game/RPG_Game_UI/MainWindow.xaml b/RPG_Game/RPG_Game_UI/MainWindow.xaml index 282d6300..962c4bf7 100644 --- a/RPG_Game/RPG_Game_UI/MainWindow.xaml +++ b/RPG_Game/RPG_Game_UI/MainWindow.xaml @@ -33,7 +33,6 @@ - @@ -53,7 +52,6 @@ public partial class MainWindow : Window { - private GameSession _gameSession; + private readonly GameSession _gameSession; public MainWindow() { From a58593ec23fe9aae2bc15ffd0fef2bdf97b3d1dd Mon Sep 17 00:00:00 2001 From: thamruicong Date: Mon, 14 Aug 2023 19:23:53 +0800 Subject: [PATCH 050/117] Display Monster --- .../Controllers/GameSession.cs | 16 +++++++++ .../RPG_Game_Engine/Factories/ItemFactory.cs | 28 ++++++++------- .../Factories/MonsterFactory.cs | 28 ++++++++++----- RPG_Game/RPG_Game_Engine/Models/Location.cs | 6 ++-- RPG_Game/RPG_Game_Engine/Models/Monster.cs | 21 ++++++++--- RPG_Game/RPG_Game_Engine/Models/World.cs | 14 ++++---- .../RPG_Game_Engine/RPG_Game_Engine.csproj | 4 +++ RPG_Game/RPG_Game_UI/MainWindow.xaml | 35 ++++++++++++++++++- 8 files changed, 114 insertions(+), 38 deletions(-) diff --git a/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs b/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs index 6d1d6987..5b0371c4 100644 --- a/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs +++ b/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs @@ -10,6 +10,7 @@ namespace Engine.Controllers public class GameSession : BaseNotification { private Location _currentLocation; + private Monster? _currentMonster; public Player CurrentPlayer { get; set; } public Location CurrentLocation { @@ -20,6 +21,17 @@ public Location CurrentLocation OnPropertyChanged(nameof(CurrentLocation)); } } + public Monster? CurrentMonster + { + get { return _currentMonster; } + set + { + _currentMonster = value; + OnPropertyChanged(nameof(CurrentMonster)); + OnPropertyChanged(nameof(IsInBattle)); + } + } + public bool IsInBattle => CurrentMonster != null; internal World CurrentWorld { get; set; } public GameSession() @@ -33,16 +45,20 @@ public GameSession() this.CurrentPlayer.Inventory.AddItem(ItemFactory.CreateGameItemGroup(1002, 1)); this.CurrentPlayer.Inventory.AddItem(ItemFactory.CreateGameItemGroup(9002, 5)); this.CurrentPlayer.Inventory.AddItem(ItemFactory.CreateGameItemGroup(9006, 8)); + + this.CurrentMonster = MonsterFactory.GetRandomMonster(); } public void OnClick_Move() { this.CurrentLocation = this.CurrentWorld.GetLocation(this.CurrentLocation); + this.CurrentMonster = MonsterFactory.GetRandomMonster(); } public void OnClick_Shop() { this.CurrentLocation = this.CurrentWorld.GetLocation("Shop"); + this.CurrentMonster = null; } } } \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/Factories/ItemFactory.cs b/RPG_Game/RPG_Game_Engine/Factories/ItemFactory.cs index e391a1e5..6e5feaf0 100644 --- a/RPG_Game/RPG_Game_Engine/Factories/ItemFactory.cs +++ b/RPG_Game/RPG_Game_Engine/Factories/ItemFactory.cs @@ -9,29 +9,31 @@ namespace Engine.Factories { internal static class ItemFactory { - private readonly static List _gameItems; + private readonly static Dictionary _gameItems; static ItemFactory() { - _gameItems = new List + _gameItems = new Dictionary { - new Weapon(1001, "Pointy Stick", 1, 2), - new Weapon(1002, "Rusty Sword", 2, 3, 5), - new Craftable(9001, "Snakeskin", "The skin of a snake. Looks durable.", 2), - new Craftable(9002, "Snake venom", "One drop can kill.", 10), - new Craftable(9003, "Rat tail", "Looks gross.", 1), - new Craftable(9004, "Rat meat", "Edible, but not very tasty.", 2), - new Craftable(9005, "Spider fang", "Sharp and poisonous.", 5), - new Craftable(9006, "Spider silk", "Strong and flexible.", 3), + {1001, new Weapon(1001, "Pointy Stick", 1, 2)}, + {1002, new Weapon(1002, "Rusty Sword", 2, 3, 5)}, + {9001, new Craftable(9001, "Snakeskin", "The skin of a snake. Looks durable.", 2)}, + {9002, new Craftable(9002, "Snake venom", "One drop can kill.", 10)}, + {9003, new Craftable(9003, "Rat tail", "Looks gross.", 1)}, + {9004, new Craftable(9004, "Rat meat", "Edible, but not very tasty.", 2)}, + {9005, new Craftable(9005, "Spider fang", "Sharp and poisonous.", 5)}, + {9006, new Craftable(9006, "Spider silk", "Strong and flexible.", 3)}, }; } internal static GameItem CreateGameItem(int itemID) { - GameItem item = _gameItems.FirstOrDefault(item => item.ItemID == itemID) - ?? throw new ArgumentException(string.Format("Item ID {0} does not exist", itemID)); + if (!_gameItems.ContainsKey(itemID)) + { + throw new ArgumentException(string.Format("Item ID {0} does not exist", itemID)); + } - return item.Clone(); + return _gameItems[itemID].Clone(); } internal static GameItemGroup CreateGameItemGroup(int itemID, int quantity) diff --git a/RPG_Game/RPG_Game_Engine/Factories/MonsterFactory.cs b/RPG_Game/RPG_Game_Engine/Factories/MonsterFactory.cs index 7aebd9bb..f4ca9477 100644 --- a/RPG_Game/RPG_Game_Engine/Factories/MonsterFactory.cs +++ b/RPG_Game/RPG_Game_Engine/Factories/MonsterFactory.cs @@ -9,24 +9,34 @@ namespace Engine.Factories { internal static class MonsterFactory { - private readonly static List _monsters; + private readonly static Dictionary _monsters; static MonsterFactory() { - _monsters = new List + _monsters = new Dictionary { - new Monster(4001, "Snake", "Snake", 8, 10, 10), - new Monster(4002, "Rat", "Rat", 5, 5, 5), - new Monster(4003, "Spider", "Spider", 10, 10, 10), + {4001, new Monster(4001, "Snake", "Snake", 8, 10, 10)}, + {4002, new Monster(4002, "Rat", "Rat", 5, 5, 5)}, + {4003, new Monster(4003, "Spider", "Spider", 10, 10, 10)}, }; } - internal static Monster GetMonster(int monsterID) + internal static Monster CreateMonster(int monsterID) { - Monster monster = _monsters.FirstOrDefault(monster => monster.ID == monsterID) - ?? throw new ArgumentException(string.Format("Monster ID {0} does not exist", monsterID)); + if (!_monsters.ContainsKey(monsterID)) + { + throw new ArgumentException(string.Format("Monster ID {0} does not exist", monsterID)); + } - return monster.Clone(); + return _monsters[monsterID].Clone(); + } + + internal static Monster GetRandomMonster() + { + Random random = new(); + int randomMonsterKeyID = random.Next(_monsters.Count); + + return CreateMonster(_monsters.Keys.ElementAt(randomMonsterKeyID)); } } } \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/Models/Location.cs b/RPG_Game/RPG_Game_Engine/Models/Location.cs index 58b22722..f2c4845a 100644 --- a/RPG_Game/RPG_Game_Engine/Models/Location.cs +++ b/RPG_Game/RPG_Game_Engine/Models/Location.cs @@ -7,9 +7,9 @@ namespace Engine.Models { public class Location { - public string Name { get; set; } - public string Description { get; set; } - public string ImageName { get; set; } + public string Name { get; private set; } + public string Description { get; private set; } + public string ImageName { get; private set; } public Location(string name, string description, string imageName) { diff --git a/RPG_Game/RPG_Game_Engine/Models/Monster.cs b/RPG_Game/RPG_Game_Engine/Models/Monster.cs index 70617858..397a6388 100644 --- a/RPG_Game/RPG_Game_Engine/Models/Monster.cs +++ b/RPG_Game/RPG_Game_Engine/Models/Monster.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; @@ -8,7 +9,7 @@ namespace Engine.Models public class Monster : BaseNotification, ICloneable { private int _currentHitPoints; - public int ID { get; private set; } + public int MonsterID { get; private set; } public string Name { get; private set; } public string ImageName { get; private set; } public int MaximumHitPoints { get; private set; } @@ -25,9 +26,9 @@ public int CurrentHitPoints public int RewardGold { get; private set; } public Inventory LootTable { get; private set; } - public Monster(int id, string name, string imageName, int maximumHitPoints, int rewardExperiencePoints, int rewardGold) + public Monster(int monsterID, string name, string imageName, int maximumHitPoints, int rewardExperiencePoints, int rewardGold) { - this.ID = id; + this.MonsterID = monsterID; this.Name = name; this.ImageName = string.Format("pack://application:,,,/RPG_Game_Engine;component/Images/Monsters/{0}.png", imageName); this.MaximumHitPoints = maximumHitPoints; @@ -37,9 +38,21 @@ public Monster(int id, string name, string imageName, int maximumHitPoints, int this.LootTable = new Inventory(); } + private Monster(int monsterID, string name, int maximumHitPoints, int rewardExperiencePoints, int rewardGold, string imageName) + { + this.MonsterID = monsterID; + this.Name = name; + this.ImageName = imageName; + this.MaximumHitPoints = maximumHitPoints; + this.CurrentHitPoints = maximumHitPoints; + this.RewardExperiencePoints = rewardExperiencePoints; + this.RewardGold = rewardGold; + this.LootTable = new Inventory(); + } + public Monster Clone() { - return new Monster(this.ID, this.Name, this.ImageName, this.MaximumHitPoints, this.RewardExperiencePoints, this.RewardGold); + return new Monster(this.MonsterID, this.Name, this.MaximumHitPoints, this.RewardExperiencePoints, this.RewardGold, this.ImageName); } object ICloneable.Clone() diff --git a/RPG_Game/RPG_Game_Engine/Models/World.cs b/RPG_Game/RPG_Game_Engine/Models/World.cs index 00f5572c..6f985a86 100644 --- a/RPG_Game/RPG_Game_Engine/Models/World.cs +++ b/RPG_Game/RPG_Game_Engine/Models/World.cs @@ -27,24 +27,22 @@ internal Location GetLocation(Location prev) while (true) { Random random = new(); - int index = random.Next(0, _locations.Count); + int index = random.Next(_locations.Count); if (0 <= index && index < _locations.Count && _locations[index] != prev) { - return _locations[index]; + return _locations.ElementAt(index); } } } internal Location GetLocation(string key) { - try + if (!_special_locations.ContainsKey(key)) { - return _special_locations[key]; - } - catch (KeyNotFoundException e) - { - throw new KeyNotFoundException(string.Format("Key not found in {0} dictionary", nameof(_special_locations)), e); + throw new KeyNotFoundException(string.Format("Key not found in {0} dictionary", nameof(_special_locations))); } + + return _special_locations[key]; } } } \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj b/RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj index 056bf455..6d6c1689 100644 --- a/RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj +++ b/RPG_Game/RPG_Game_Engine/RPG_Game_Engine.csproj @@ -16,6 +16,10 @@ + + + + diff --git a/RPG_Game/RPG_Game_UI/MainWindow.xaml b/RPG_Game/RPG_Game_UI/MainWindow.xaml index b47ae7ee..e118fd01 100644 --- a/RPG_Game/RPG_Game_UI/MainWindow.xaml +++ b/RPG_Game/RPG_Game_UI/MainWindow.xaml @@ -6,6 +6,9 @@ xmlns:local="clr-namespace:RPG_Game_UI" mc:Ignorable="d" Title="RPG Game" Height="700" Width="800" ResizeMode="CanMinimize"> + + + @@ -17,6 +20,7 @@ + @@ -25,6 +29,7 @@ + @@ -54,6 +59,7 @@ + @@ -91,8 +97,10 @@ + + @@ -108,9 +116,34 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + From b160bf2bc4f98de634a01fa5c45827ab04feb0e3 Mon Sep 17 00:00:00 2001 From: thamruicong Date: Tue, 15 Aug 2023 13:52:24 +0800 Subject: [PATCH 051/117] Features added: - Add Gamelogs - Add Monster DropChance and LootTable - TODO: Link together Converters to XAML UI --- .../Controllers/GameSession.cs | 24 ++++++++++--- .../EventArgs/GameMessageEventArgs.cs | 12 +++++++ .../Factories/MonsterFactory.cs | 17 +++++++-- RPG_Game/RPG_Game_Engine/Models/Inventory.cs | 2 ++ .../Models/Items/DropChance.cs | 25 +++++++++++++ .../RPG_Game_Engine/Models/Items/LootTable.cs | 35 ++++++++++++++++++ .../Models/{ => Monsters}/Monster.cs | 26 ++++++++++---- RPG_Game/RPG_Game_Engine/Models/Player.cs | 23 ++++++++---- .../RPG_Game_UI/Converters/BooleanInverter.cs | 27 ++++++++++++++ .../Converters/ValueConverterGroup.cs | 26 ++++++++++++++ RPG_Game/RPG_Game_UI/MainWindow.xaml | 36 +++++++++++++++---- RPG_Game/RPG_Game_UI/MainWindow.xaml.cs | 8 +++++ 12 files changed, 234 insertions(+), 27 deletions(-) create mode 100644 RPG_Game/RPG_Game_Engine/EventArgs/GameMessageEventArgs.cs create mode 100644 RPG_Game/RPG_Game_Engine/Models/Items/DropChance.cs create mode 100644 RPG_Game/RPG_Game_Engine/Models/Items/LootTable.cs rename RPG_Game/RPG_Game_Engine/Models/{ => Monsters}/Monster.cs (62%) create mode 100644 RPG_Game/RPG_Game_UI/Converters/BooleanInverter.cs create mode 100644 RPG_Game/RPG_Game_UI/Converters/ValueConverterGroup.cs diff --git a/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs b/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs index 5b0371c4..651adb84 100644 --- a/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs +++ b/RPG_Game/RPG_Game_Engine/Controllers/GameSession.cs @@ -3,12 +3,18 @@ using System.Text; using System.Threading.Tasks; using Engine.Models; +using Engine.Models.Monsters; using Engine.Factories; +using Engine.EventArgs; namespace Engine.Controllers { public class GameSession : BaseNotification { + public event EventHandler OnMessageRaised; + + #region Properties + private Location _currentLocation; private Monster? _currentMonster; public Player CurrentPlayer { get; set; } @@ -28,37 +34,47 @@ public Monster? CurrentMonster { _currentMonster = value; OnPropertyChanged(nameof(CurrentMonster)); + OnPropertyChanged(nameof(HasMonster)); OnPropertyChanged(nameof(IsInBattle)); } } - public bool IsInBattle => CurrentMonster != null; + public bool HasMonster => CurrentMonster != null; + public bool IsInBattle => HasMonster && CurrentMonster?.CurrentHitPoints > 0; internal World CurrentWorld { get; set; } + #endregion + public GameSession() { this.CurrentPlayer = new Player("Scott", "Fighter", 10, 0, 1, 100000); this.CurrentWorld = WorldFactory.CreateWorld(); - this.CurrentLocation = this.CurrentWorld.GetLocation(this.CurrentLocation); this.CurrentPlayer.Inventory.AddItem(ItemFactory.CreateGameItemGroup(1001, 1)); this.CurrentPlayer.Inventory.AddItem(ItemFactory.CreateGameItemGroup(1001, 2)); this.CurrentPlayer.Inventory.AddItem(ItemFactory.CreateGameItemGroup(1002, 1)); this.CurrentPlayer.Inventory.AddItem(ItemFactory.CreateGameItemGroup(9002, 5)); this.CurrentPlayer.Inventory.AddItem(ItemFactory.CreateGameItemGroup(9006, 8)); - - this.CurrentMonster = MonsterFactory.GetRandomMonster(); } public void OnClick_Move() { this.CurrentLocation = this.CurrentWorld.GetLocation(this.CurrentLocation); this.CurrentMonster = MonsterFactory.GetRandomMonster(); + + RaiseMessage($"You see a {this.CurrentMonster.Name} here!"); } public void OnClick_Shop() { this.CurrentLocation = this.CurrentWorld.GetLocation("Shop"); this.CurrentMonster = null; + + RaiseMessage($"You have entered the shop!"); + } + + private void RaiseMessage(string message) + { + OnMessageRaised?.Invoke(this, new GameMessageEventArgs(message)); } } } \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/EventArgs/GameMessageEventArgs.cs b/RPG_Game/RPG_Game_Engine/EventArgs/GameMessageEventArgs.cs new file mode 100644 index 00000000..acf0d481 --- /dev/null +++ b/RPG_Game/RPG_Game_Engine/EventArgs/GameMessageEventArgs.cs @@ -0,0 +1,12 @@ +namespace Engine.EventArgs +{ + public class GameMessageEventArgs : System.EventArgs + { + public string Message { get; private set; } + + public GameMessageEventArgs(string message) + { + this.Message = message; + } + } +} \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/Factories/MonsterFactory.cs b/RPG_Game/RPG_Game_Engine/Factories/MonsterFactory.cs index f4ca9477..53dde9e2 100644 --- a/RPG_Game/RPG_Game_Engine/Factories/MonsterFactory.cs +++ b/RPG_Game/RPG_Game_Engine/Factories/MonsterFactory.cs @@ -3,6 +3,8 @@ using System.Text; using System.Threading.Tasks; using System.Collections.Generic; +using Engine.Models.Monsters; +using Engine.Models.Items; using Engine.Models; namespace Engine.Factories @@ -15,9 +17,18 @@ static MonsterFactory() { _monsters = new Dictionary { - {4001, new Monster(4001, "Snake", "Snake", 8, 10, 10)}, - {4002, new Monster(4002, "Rat", "Rat", 5, 5, 5)}, - {4003, new Monster(4003, "Spider", "Spider", 10, 10, 10)}, + {4001, new Monster(4001, "Snake", "Snake", 8, 1, 2, 10, 10, new(new(){ + new DropChance(9001, 100, 1, 3), + new DropChance(9002, 25, 1, 1), + }))}, + {4002, new Monster(4002, "Rat", "Rat", 5, 2, 2, 5, 5, new(new(){ + new DropChance(9003, 100, 1, 1), + new DropChance(9004, 75, 1, 1), + }))}, + {4003, new Monster(4003, "Spider", "Spider", 10, 1, 5, 10, 10, new(new(){ + new DropChance(9005, 50, 1, 2), + new DropChance(9006, 100, 1, 1), + }))}, }; } diff --git a/RPG_Game/RPG_Game_Engine/Models/Inventory.cs b/RPG_Game/RPG_Game_Engine/Models/Inventory.cs index 7d19fc96..67faee57 100644 --- a/RPG_Game/RPG_Game_Engine/Models/Inventory.cs +++ b/RPG_Game/RPG_Game_Engine/Models/Inventory.cs @@ -13,6 +13,8 @@ public class Inventory public ObservableCollection Items { get { return _items; } } + public ObservableCollection Weapons => new(_items.Where(item => item.Item is Weapon)); + public Inventory() { _items = new ObservableCollection(); diff --git a/RPG_Game/RPG_Game_Engine/Models/Items/DropChance.cs b/RPG_Game/RPG_Game_Engine/Models/Items/DropChance.cs new file mode 100644 index 00000000..5ae09b10 --- /dev/null +++ b/RPG_Game/RPG_Game_Engine/Models/Items/DropChance.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Engine.Models.Items +{ + public class DropChance + { + public int ItemID { get; private set; } + + // Out of 100 + public int DropPercentage { get; private set; } + public int MinimumQuantity { get; private set; } + public int MaximumQuantity { get; private set; } + + public DropChance(int itemID, int dropPercentage, int MinimumQuantity, int MaximumQuantity) + { + this.ItemID = itemID; + this.DropPercentage = dropPercentage; + this.MinimumQuantity = MinimumQuantity; + this.MaximumQuantity = MaximumQuantity; + } + } +} \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/Models/Items/LootTable.cs b/RPG_Game/RPG_Game_Engine/Models/Items/LootTable.cs new file mode 100644 index 00000000..7df05eec --- /dev/null +++ b/RPG_Game/RPG_Game_Engine/Models/Items/LootTable.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Collections.Generic; +using Engine.Factories; + +namespace Engine.Models.Items +{ + public class LootTable + { + public List DropChances { get; private set; } + + public LootTable(List dropChances) + { + DropChances = dropChances; + } + + public Inventory RollLoot() + { + Inventory inventory = new(); + Random random = new(); + + foreach (DropChance dropChance in DropChances) + { + if (random.Next(1, 101) <= dropChance.DropPercentage) + { + inventory.AddItem(ItemFactory.CreateGameItemGroup(dropChance.ItemID, random.Next(dropChance.MinimumQuantity, dropChance.MaximumQuantity + 1))); + } + } + + return inventory; + } + } +} \ No newline at end of file diff --git a/RPG_Game/RPG_Game_Engine/Models/Monster.cs b/RPG_Game/RPG_Game_Engine/Models/Monsters/Monster.cs similarity index 62% rename from RPG_Game/RPG_Game_Engine/Models/Monster.cs rename to RPG_Game/RPG_Game_Engine/Models/Monsters/Monster.cs index 397a6388..0c25dc57 100644 --- a/RPG_Game/RPG_Game_Engine/Models/Monster.cs +++ b/RPG_Game/RPG_Game_Engine/Models/Monsters/Monster.cs @@ -3,8 +3,9 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; +using Engine.Models.Items; -namespace Engine.Models +namespace Engine.Models.Monsters { public class Monster : BaseNotification, ICloneable { @@ -22,37 +23,48 @@ public int CurrentHitPoints OnPropertyChanged(nameof(CurrentHitPoints)); } } + public int MinimumDamage { get; private set; } + public int MaximumDamage { get; private set; } public int RewardExperiencePoints { get; private set; } public int RewardGold { get; private set; } - public Inventory LootTable { get; private set; } + public LootTable LootTable { get; private set;} + public Inventory? Inventory { get; private set; } - public Monster(int monsterID, string name, string imageName, int maximumHitPoints, int rewardExperiencePoints, int rewardGold) + // Constructor for initializing a monster with a loot table + public Monster(int monsterID, string name, string imageName, int maximumHitPoints, int minimumDamage, int maximumDamage, int rewardExperiencePoints, int rewardGold, LootTable lootTable) { this.MonsterID = monsterID; this.Name = name; this.ImageName = string.Format("pack://application:,,,/RPG_Game_Engine;component/Images/Monsters/{0}.png", imageName); this.MaximumHitPoints = maximumHitPoints; this.CurrentHitPoints = maximumHitPoints; + this.MinimumDamage = minimumDamage; + this.MaximumDamage = maximumDamage; this.RewardExperiencePoints = rewardExperiencePoints; this.RewardGold = rewardGold; - this.LootTable = new Inventory(); + this.LootTable = lootTable; + this.Inventory = null; } - private Monster(int monsterID, string name, int maximumHitPoints, int rewardExperiencePoints, int rewardGold, string imageName) + // Constructor for cloning a monster + private Monster(int monsterID, string name, int maximumHitPoints, int minimumDamage, int maximumDamage, int rewardExperiencePoints, int rewardGold, string imageName, LootTable lootTable) { this.MonsterID = monsterID; this.Name = name; this.ImageName = imageName; this.MaximumHitPoints = maximumHitPoints; this.CurrentHitPoints = maximumHitPoints; + this.MinimumDamage = minimumDamage; + this.MaximumDamage = maximumDamage; this.RewardExperiencePoints = rewardExperiencePoints; this.RewardGold = rewardGold; - this.LootTable = new Inventory(); + this.LootTable = lootTable; + this.Inventory = LootTable.RollLoot(); } public Monster Clone() { - return new Monster(this.MonsterID, this.Name, this.MaximumHitPoints, this.RewardExperiencePoints, this.RewardGold, this.ImageName); + return new Monster(this.MonsterID, this.Name, this.MaximumHitPoints, this.MinimumDamage, this.MaximumDamage, this.RewardExperiencePoints, this.RewardGold, this.ImageName, this.LootTable); } object ICloneable.Clone() diff --git a/RPG_Game/RPG_Game_Engine/Models/Player.cs b/RPG_Game/RPG_Game_Engine/Models/Player.cs index dba44ef2..1a89c783 100644 --- a/RPG_Game/RPG_Game_Engine/Models/Player.cs +++ b/RPG_Game/RPG_Game_Engine/Models/Player.cs @@ -8,7 +8,8 @@ namespace Engine.Models public class Player : BaseNotification { private string _characterClass; - private int _hitPoints; + private int _currentHitPoints; + private int _maximumHitPoints; private int _experiencePoints; private int _level; private int _gold; @@ -23,13 +24,22 @@ public string CharacterClass OnPropertyChanged(nameof(CharacterClass)); } } - public int HitPoints + public int CurrentHitPoints { - get { return _hitPoints; } + get { return _currentHitPoints; } set { - _hitPoints = value; - OnPropertyChanged(nameof(HitPoints)); + _currentHitPoints = value; + OnPropertyChanged(nameof(CurrentHitPoints)); + } + } + public int MaximumHitPoints + { + get { return _maximumHitPoints; } + set + { + _maximumHitPoints = value; + OnPropertyChanged(nameof(MaximumHitPoints)); } } public int ExperiencePoints @@ -66,7 +76,8 @@ public Player(string name, string characterClass, int hitPoints, int experienceP { this.Name = name; this.CharacterClass = characterClass; - this.HitPoints = hitPoints; + this.CurrentHitPoints = hitPoints; + this.MaximumHitPoints = hitPoints; this.ExperiencePoints = experiencePoints; this.Level = level; this.Gold = gold; diff --git a/RPG_Game/RPG_Game_UI/Converters/BooleanInverter.cs b/RPG_Game/RPG_Game_UI/Converters/BooleanInverter.cs new file mode 100644 index 00000000..32ff5488 --- /dev/null +++ b/RPG_Game/RPG_Game_UI/Converters/BooleanInverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +// copied from https://stackoverflow.com/questions/1039636/how-to-bind-inverse-boolean-properties-in-wpf +namespace RPG_Game_UI.Converters +{ + public class BooleanInverter : IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (targetType != typeof(bool)) + throw new InvalidOperationException("The target must be a boolean"); + + return !(bool) value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion IValueConverter Members + } +} \ No newline at end of file diff --git a/RPG_Game/RPG_Game_UI/Converters/ValueConverterGroup.cs b/RPG_Game/RPG_Game_UI/Converters/ValueConverterGroup.cs new file mode 100644 index 00000000..4f5653f7 --- /dev/null +++ b/RPG_Game/RPG_Game_UI/Converters/ValueConverterGroup.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Windows.Data; + +// copied from https://stackoverflow.com/questions/2607490/is-there-a-way-to-chain-multiple-value-converters-in-xaml +namespace RPG_Game_UI.Converters +{ + public class ValueConverterGroup : List, IValueConverter + { + #region IValueConverter Members + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return this.Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, culture)); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + #endregion IValueConverter Members + } +} \ No newline at end of file diff --git a/RPG_Game/RPG_Game_UI/MainWindow.xaml b/RPG_Game/RPG_Game_UI/MainWindow.xaml index e118fd01..bef866d0 100644 --- a/RPG_Game/RPG_Game_UI/MainWindow.xaml +++ b/RPG_Game/RPG_Game_UI/MainWindow.xaml @@ -4,11 +4,20 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:RPG_Game_UI" + xmlns:viewModels="clr-namespace:Engine.Controllers;assembly=Engine" + xmlns:Converters="clr-namespace:RPG_Game_UI.Converters" + d:DataContext="{d:DesignInstance viewModels:GameSession}" mc:Ignorable="d" Title="RPG Game" Height="700" Width="800" ResizeMode="CanMinimize"> + + + + + + @@ -50,7 +59,13 @@ -