From 949ad5eabba49006dc187576f3ed03edfc5fd0e6 Mon Sep 17 00:00:00 2001 From: Lumine_ Date: Mon, 5 Aug 2024 11:35:00 +0800 Subject: [PATCH 1/3] Initial commit for app-server. Added directory structure --- README.md | 11 + app-server/games/__init__.py | 1 + app-server/games/baccarat/__init__.py | 1 + app-server/games/baccarat/extract.py | 4 + app-server/games/baccarat/load.py | 4 + app-server/games/baccarat/pipeline.py | 11 + app-server/games/baccarat/transform.py | 4 + app-server/server/__init__.py | 1 + app-server/server/app.py | 12 + .../baccarat/helper_functions_demo.ipynb | 511 ------------------ 10 files changed, 49 insertions(+), 511 deletions(-) create mode 100644 app-server/games/__init__.py create mode 100644 app-server/games/baccarat/__init__.py create mode 100644 app-server/games/baccarat/extract.py create mode 100644 app-server/games/baccarat/load.py create mode 100644 app-server/games/baccarat/pipeline.py create mode 100644 app-server/games/baccarat/transform.py create mode 100644 app-server/server/__init__.py create mode 100644 app-server/server/app.py delete mode 100644 game-server/games/baccarat/helper_functions_demo.ipynb diff --git a/README.md b/README.md index 2e65818..14edd37 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,14 @@ uvicorn server.app:app 2. All FastAPI logic should live on `game-server/server` directory. 3. All modules in `games-server/games` should return a dataclass. Converting it to `json` will be handled by `games-server/server` 4. We can *intentionally* introduce errors into the game logic so that, when using machine learning, we can detect suspicious transactions. + +### Developing the `app-server` +```bash +cd app-server +# run in a virtual environment +pip install -r requirements.txt +python server/app.py +``` + +1. ETL Pipeline logic should live on `app-server/game` directory +2. All invocation of ETL Pipeline for each game should live on `app-server/server` directory. diff --git a/app-server/games/__init__.py b/app-server/games/__init__.py new file mode 100644 index 0000000..a1bc617 --- /dev/null +++ b/app-server/games/__init__.py @@ -0,0 +1 @@ +from .baccarat import pipeline_baccarat \ No newline at end of file diff --git a/app-server/games/baccarat/__init__.py b/app-server/games/baccarat/__init__.py new file mode 100644 index 0000000..31dbea9 --- /dev/null +++ b/app-server/games/baccarat/__init__.py @@ -0,0 +1 @@ +from .pipeline import pipeline_baccarat \ No newline at end of file diff --git a/app-server/games/baccarat/extract.py b/app-server/games/baccarat/extract.py new file mode 100644 index 0000000..8e54e38 --- /dev/null +++ b/app-server/games/baccarat/extract.py @@ -0,0 +1,4 @@ +import pandas as pd + +def extract(): + pass \ No newline at end of file diff --git a/app-server/games/baccarat/load.py b/app-server/games/baccarat/load.py new file mode 100644 index 0000000..658c695 --- /dev/null +++ b/app-server/games/baccarat/load.py @@ -0,0 +1,4 @@ +import pandas as pd + +def load(): + pass \ No newline at end of file diff --git a/app-server/games/baccarat/pipeline.py b/app-server/games/baccarat/pipeline.py new file mode 100644 index 0000000..3583fc5 --- /dev/null +++ b/app-server/games/baccarat/pipeline.py @@ -0,0 +1,11 @@ +import pandas as pd +from .extract import extract +from .transform import transform +from .load import load + + +def pipeline_baccarat(): + extract() + transform() + load() + print('Pipeline completed') \ No newline at end of file diff --git a/app-server/games/baccarat/transform.py b/app-server/games/baccarat/transform.py new file mode 100644 index 0000000..62ef16a --- /dev/null +++ b/app-server/games/baccarat/transform.py @@ -0,0 +1,4 @@ +import pandas as pd + +def transform(): + pass \ No newline at end of file diff --git a/app-server/server/__init__.py b/app-server/server/__init__.py new file mode 100644 index 0000000..49e2030 --- /dev/null +++ b/app-server/server/__init__.py @@ -0,0 +1 @@ +from games import pipeline_baccarat \ No newline at end of file diff --git a/app-server/server/app.py b/app-server/server/app.py new file mode 100644 index 0000000..96f83f7 --- /dev/null +++ b/app-server/server/app.py @@ -0,0 +1,12 @@ +import os +import sys +import pandas as pd + +# Add the parent directory of 'games' (i.e., 'app-server') to sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +# Import modules +from games.baccarat import pipeline_baccarat + + +pipeline_baccarat() diff --git a/game-server/games/baccarat/helper_functions_demo.ipynb b/game-server/games/baccarat/helper_functions_demo.ipynb deleted file mode 100644 index e739636..0000000 --- a/game-server/games/baccarat/helper_functions_demo.ipynb +++ /dev/null @@ -1,511 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 146, - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "import itertools\n", - "import random\n", - "from dataclasses import dataclass" - ] - }, - { - "cell_type": "code", - "execution_count": 161, - "metadata": {}, - "outputs": [], - "source": [ - "@dataclass\n", - "class GameResult:\n", - " player_hand: tuple\n", - " player_hand_value: int\n", - " banker_hand: tuple\n", - " banker_hand_value: int\n", - " last_action: str\n", - " winner: str" - ] - }, - { - "cell_type": "code", - "execution_count": 148, - "metadata": {}, - "outputs": [], - "source": [ - "@dataclass\n", - "class BaccaratResult:\n", - " game: str\n", - " game_id: str\n", - " player_id: str\n", - " status: str\n", - " # start_time: datetime\n", - " # end_time: datetime\n", - " player_hand: list\n", - " player_hand_value: int\n", - " banker_hand: list\n", - " banker_hand_value: int\n", - " player_beginning_balance: float\n", - " player_wager: float\n", - " player_bet: str\n", - " player_payout: float\n", - " player_ending_balance: float\n", - " last_action: str\n", - " game_outcome: str" - ] - }, - { - "cell_type": "code", - "execution_count": 149, - "metadata": {}, - "outputs": [], - "source": [ - "def build_deck():\n", - "\n", - " suits = ['Hearts', 'Diamonds', 'Clubs', 'Spades']\n", - " ranks = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']\n", - " original_deck = [f\"{rank} of {suit}\" for suit in suits for rank in ranks]\n", - " \n", - " return original_deck" - ] - }, - { - "cell_type": "code", - "execution_count": 150, - "metadata": {}, - "outputs": [], - "source": [ - "def build_combinations(deck):\n", - "\n", - " card_combinations = list(itertools.combinations(deck, 2))\n", - " card_combinations_df = pd.DataFrame(card_combinations, columns = ['card_1', 'card_2'])\n", - "\n", - " card_combinations_df['card_combination'] = card_combinations_df.apply(\n", - " lambda row:\n", - " (row['card_1'], row['card_2']), \n", - " axis = 1\n", - " )\n", - "\n", - " card_combinations_df['card_1_value'] = card_combinations_df['card_1'].apply(\n", - " lambda x: \n", - " int(x.split(' ')[0]) if x.split(' ')[0].isdigit() else 1 if x.split(' ')[0] == 'A' else 10\n", - " )\n", - "\n", - " card_combinations_df['card_2_value'] = card_combinations_df['card_2'].apply(\n", - " lambda x: \n", - " int(x.split(' ')[0]) if x.split(' ')[0].isdigit() else 1 if x.split(' ')[0] == 'A' else 10\n", - " )\n", - "\n", - " card_combinations_df['card_combination_value'] = card_combinations_df.apply(\n", - " lambda row:\n", - " int(str(row['card_1_value'] + row['card_2_value'])[-1]),\n", - " axis = 1\n", - " )\n", - "\n", - " deck_dict = dict(zip(card_combinations_df['card_combination'], card_combinations_df['card_combination_value']))\n", - "\n", - " return card_combinations, card_combinations_df, deck_dict" - ] - }, - { - "cell_type": "code", - "execution_count": 151, - "metadata": {}, - "outputs": [], - "source": [ - "def draw_player_hand():\n", - " \n", - " deck = build_deck()\n", - " card_combinations, card_combinations_df, deck_dict = build_combinations(deck)\n", - "\n", - " player_hand = tuple(random.choice(card_combinations))\n", - " player_hand_value = int(str(deck_dict[player_hand])[-1])\n", - "\n", - " return player_hand, player_hand_value" - ] - }, - { - "cell_type": "code", - "execution_count": 152, - "metadata": {}, - "outputs": [], - "source": [ - "def rebuild_deck(original_deck, excluded_cards: list):\n", - " \n", - " deck_remaining = [card for card in original_deck if card not in excluded_cards]\n", - "\n", - " return deck_remaining" - ] - }, - { - "cell_type": "code", - "execution_count": 153, - "metadata": {}, - "outputs": [], - "source": [ - "def draw_banker_hand(player_hand, with_weights = 'No'):\n", - "\n", - " original_deck = build_deck()\n", - " deck_remaining = rebuild_deck(original_deck, player_hand)\n", - " card_combinations, card_combinations_df, deck_dict = build_combinations(deck_remaining)\n", - "\n", - " if with_weights != 'No':\n", - " weights = np.where(card_combinations_df['card_combination_value'] >= 7, 3, 1)\n", - "\n", - " else:\n", - " weights = np.ones(len(card_combinations))\n", - "\n", - " \n", - " banker_hand = tuple(random.choices(card_combinations, weights = weights)[0])\n", - " banker_hand_value = int(str(deck_dict[banker_hand])[-1])\n", - "\n", - " return banker_hand, banker_hand_value" - ] - }, - { - "cell_type": "code", - "execution_count": 154, - "metadata": {}, - "outputs": [], - "source": [ - "def draw_player(drawn_cards, player_hand, player_hand_value):\n", - " \n", - " original_deck = build_deck()\n", - " deck_remaining = rebuild_deck(original_deck, drawn_cards)\n", - " card_combinations, card_combinations_df, deck_dict = build_combinations(deck_remaining)\n", - "\n", - " player_draw = (random.choice(deck_remaining), )\n", - " player_draw_rank = player_draw[0].split(' ')[0]\n", - " player_draw_value = int(player_draw_rank) if player_draw_rank.isdigit() else 1 if player_draw_rank == 'A' else 10\n", - " player_hand = player_hand + player_draw \n", - " player_hand_value = int(str(player_hand_value + player_draw_value)[-1])\n", - "\n", - " return player_hand, player_hand_value, player_draw" - ] - }, - { - "cell_type": "code", - "execution_count": 155, - "metadata": {}, - "outputs": [], - "source": [ - "def draw_banker(drawn_cards: list, banker_hand, banker_hand_value):\n", - " \n", - " original_deck = build_deck()\n", - " deck_remaining = rebuild_deck(original_deck, drawn_cards)\n", - " card_combinations, card_combinations_df, deck_dict = build_combinations(deck_remaining)\n", - "\n", - " banker_draw = (random.choice(deck_remaining), )\n", - " banker_draw_rank = banker_draw[0].split(' ')[0]\n", - " banker_draw_value = int(banker_draw_rank) if banker_draw_rank.isdigit() else 1 if banker_draw_rank == 'A' else 10\n", - " banker_hand = banker_hand + banker_draw \n", - " banker_hand_value = int(str(banker_hand_value + banker_draw_value)[-1])\n", - "\n", - " return banker_hand, banker_hand_value, banker_draw" - ] - }, - { - "cell_type": "code", - "execution_count": 156, - "metadata": {}, - "outputs": [], - "source": [ - "def announce_winner(player_hand_value, banker_hand_value):\n", - " winner = str()\n", - "\n", - " if player_hand_value > banker_hand_value:\n", - " winner = 'Player'\n", - "\n", - " elif player_hand_value < banker_hand_value:\n", - " winner = 'Banker'\n", - "\n", - " else:\n", - " winner = 'Tie'\n", - "\n", - " return winner" - ] - }, - { - "cell_type": "code", - "execution_count": 157, - "metadata": {}, - "outputs": [], - "source": [ - "def compute_payout(winner, wager, bet):\n", - " if winner == 'Player':\n", - " if bet == 'Player':\n", - " payout = wager + (wager * 1)\n", - " else:\n", - " payout = 0\n", - "\n", - " elif winner == 'Banker':\n", - " if bet == 'Banker':\n", - " payout = wager + (wager * .95)\n", - " else:\n", - " payout = 0\n", - "\n", - " elif winner == 'Tie':\n", - " if bet == 'Tie':\n", - " payout = wager + (wager * 8)\n", - " else:\n", - " payout = 0\n", - "\n", - " return payout" - ] - }, - { - "cell_type": "code", - "execution_count": 158, - "metadata": {}, - "outputs": [], - "source": [ - "def play_game(type = 'normal'):\n", - "\n", - " # 1. Draw Player Cards\n", - " player_hand, player_hand_value = draw_player_hand()\n", - " drawn_cards = player_hand\n", - " \n", - " # 2. Draw Banker Cards\n", - " if type == 'normal':\n", - " banker_hand, banker_hand_value = draw_banker_hand(player_hand, 'No')\n", - " drawn_cards += tuple(banker_hand)\n", - " \n", - " elif type == 'rigged':\n", - " banker_hand, banker_hand_value = draw_banker_hand(player_hand, with_weights='Yes')\n", - " drawn_cards += tuple(banker_hand)\n", - "\n", - " # 3. Decision logic for drawing additional card for both player and banker\n", - " if player_hand_value <= 5:\n", - " \n", - " player_hand, player_hand_value, player_draw = draw_player(drawn_cards, player_hand, player_hand_value)\n", - " drawn_cards += tuple(player_draw)\n", - " last_action = 'player_draw'\n", - "\n", - " else:\n", - " last_action = 'initial_deal'\n", - "\n", - " \n", - " if banker_hand_value <= 5:\n", - " \n", - " banker_hand, banker_hand_value, banker_draw = draw_banker(drawn_cards, banker_hand, banker_hand_value)\n", - " drawn_cards += tuple(banker_draw)\n", - " last_action = 'banker_draw'\n", - "\n", - " else:\n", - " last_action = 'initial_deal'\n", - "\n", - " winner = announce_winner(player_hand_value, banker_hand_value)\n", - "\n", - " \n", - " return GameResult(player_hand, player_hand_value, banker_hand, banker_hand_value, last_action, winner)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Player Score: 3610 | Win Rate: 0.361\n", - "Banker Score: 5151 | Win Rate: 0.5151\n", - "Tie: 1239\n" - ] - } - ], - "source": [ - "# Simulation\n", - "\n", - "player_score = 0\n", - "banker_score = 0\n", - "tie = 0\n", - "games = 10000\n", - "\n", - "for i in range(games):\n", - " player_hand, player_hand_value, banker_hand, banker_hand_value, last_action = play_game('rigged')\n", - " if player_hand_value > banker_hand_value:\n", - " player_score+=1\n", - " elif player_hand_value < banker_hand_value:\n", - " banker_score+=1\n", - " elif player_hand_value == banker_hand_value:\n", - " tie+=1\n", - "\n", - " assert len(set(player_hand).intersection(set(banker_hand))) == 0\n", - " \n", - "\n", - "print(f'Player Score: {player_score} | Win Rate: {player_score/games}')\n", - "print(f'Banker Score: {banker_score} | Win Rate: {banker_score/games}')\n", - "print(f'Tie: {tie}')" - ] - }, - { - "cell_type": "code", - "execution_count": 176, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(('9 of Clubs', '9 of Spades'), 8)" - ] - }, - "execution_count": 176, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = play_game('normal')\n", - "result.banker_hand, result.banker_hand_value" - ] - }, - { - "cell_type": "code", - "execution_count": 171, - "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "'GameResult' object is not subscriptable", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[171], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mresult\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39msplit(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m of \u001b[39m\u001b[38;5;124m'\u001b[39m)[\u001b[38;5;241m1\u001b[39m]\n", - "\u001b[1;31mTypeError\u001b[0m: 'GameResult' object is not subscriptable" - ] - } - ], - "source": [ - "result[0][0].split(' of ')[1]" - ] - }, - { - "cell_type": "code", - "execution_count": 116, - "metadata": {}, - "outputs": [], - "source": [ - "player_hand = [\n", - " {\n", - " 'value': result[0][0].split(' of ')[1],\n", - " 'rank': result[0][0].split(' of ')[0]\n", - " },\n", - " {\n", - " 'value': result[0][1].split(' of ')[1],\n", - " 'rank': result[0][1].split(' of ')[0]\n", - " },\n", - "]\n" - ] - }, - { - "cell_type": "code", - "execution_count": 117, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'value': 'Hearts', 'rank': '8'}, {'value': 'Diamonds', 'rank': '3'}]" - ] - }, - "execution_count": 117, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "player_hand" - ] - }, - { - "cell_type": "code", - "execution_count": 118, - "metadata": {}, - "outputs": [], - "source": [ - "if len(result[0]) == 3:\n", - " player_hand.append(\n", - " {\n", - " 'value': result[0][2].split(' of ')[1],\n", - " 'rank': result[0][2].split(' of ')[0]\n", - " }\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 119, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 119, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(result[0]) == 3" - ] - }, - { - "cell_type": "code", - "execution_count": 120, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'value': 'Hearts', 'rank': '8'},\n", - " {'value': 'Diamonds', 'rank': '3'},\n", - " {'value': 'Clubs', 'rank': 'A'}]" - ] - }, - "execution_count": 120, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "player_hand" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.0" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From d58ecc25f315a524e75b6705f12ffa784383648f Mon Sep 17 00:00:00 2001 From: Lumine_ <70693859+gryAI@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:36:11 +0800 Subject: [PATCH 2/3] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14edd37..189eab2 100644 --- a/README.md +++ b/README.md @@ -28,5 +28,5 @@ pip install -r requirements.txt python server/app.py ``` -1. ETL Pipeline logic should live on `app-server/game` directory +1. ETL Pipeline logic should live on `app-server/games` directory 2. All invocation of ETL Pipeline for each game should live on `app-server/server` directory. From 07f75a006317f75fa030bdf9be267b3c598fe357 Mon Sep 17 00:00:00 2001 From: Lumine_ Date: Mon, 5 Aug 2024 11:38:34 +0800 Subject: [PATCH 3/3] Removed imports --- app-server/server/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app-server/server/__init__.py b/app-server/server/__init__.py index 49e2030..e69de29 100644 --- a/app-server/server/__init__.py +++ b/app-server/server/__init__.py @@ -1 +0,0 @@ -from games import pipeline_baccarat \ No newline at end of file