fhudi commited on
Commit
7d76671
·
verified ·
1 Parent(s): cd327bf

Upload folder using huggingface_hub

Browse files
.gitignore ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ */*.DS_Store
2
+ .DS_Store
3
+
4
+ ssl/
5
+ problemsets_*
6
+
7
+ user_outputs/
8
+
9
+ .idea/
10
+
11
+ # Created by https://www.gitignore.io/api/macos,linux,django,python,pycharm
12
+
13
+ ### Django ###
14
+ *.log
15
+ *.pot
16
+ *.pyc
17
+ __pycache__/
18
+ local_settings.py
19
+ db.sqlite3
20
+ media
21
+
22
+ ### Linux ###
23
+ *~
24
+
25
+ # temporary files which can be created if a process still has a handle open of a deleted file
26
+ .fuse_hidden*
27
+
28
+ # KDE directory preferences
29
+ .directory
30
+
31
+ # Linux trash folder which might appear on any partition or disk
32
+ .Trash-*
33
+
34
+ # .nfs files are created when an open file is removed but is still being accessed
35
+ .nfs*
36
+
37
+ ### macOS ###
38
+ *.DS_Store
39
+ .AppleDouble
40
+ .LSOverride
41
+
42
+ # Icon must end with two \r
43
+ Icon
44
+
45
+ # Thumbnails
46
+ ._*
47
+
48
+ # Files that might appear in the root of a volume
49
+ .DocumentRevisions-V100
50
+ .fseventsd
51
+ .Spotlight-V100
52
+ .TemporaryItems
53
+ .Trashes
54
+ .VolumeIcon.icns
55
+ .com.apple.timemachine.donotpresent
56
+
57
+ # Directories potentially created on remote AFP share
58
+ .AppleDB
59
+ .AppleDesktop
60
+ Network Trash Folder
61
+ Temporary Items
62
+ .apdisk
63
+
64
+ ### PyCharm ###
65
+ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
66
+ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
67
+
68
+ # User-specific stuff:
69
+ .idea/**/workspace.xml
70
+ .idea/**/tasks.xml
71
+ .idea/dictionaries
72
+
73
+ # Sensitive or high-churn files:
74
+ .idea/**/dataSources/
75
+ .idea/**/dataSources.ids
76
+ .idea/**/dataSources.xml
77
+ .idea/**/dataSources.local.xml
78
+ .idea/**/sqlDataSources.xml
79
+ .idea/**/dynamic.xml
80
+ .idea/**/uiDesigner.xml
81
+
82
+ # Gradle:
83
+ .idea/**/gradle.xml
84
+ .idea/**/libraries
85
+
86
+ # CMake
87
+ cmake-build-debug/
88
+
89
+ # Mongo Explorer plugin:
90
+ .idea/**/mongoSettings.xml
91
+
92
+ ## File-based project format:
93
+ *.iws
94
+
95
+ ## Plugin-specific files:
96
+
97
+ # IntelliJ
98
+ /out/
99
+
100
+ # mpeltonen/sbt-idea plugin
101
+ .idea_modules/
102
+
103
+ # JIRA plugin
104
+ atlassian-ide-plugin.xml
105
+
106
+ # Cursive Clojure plugin
107
+ .idea/replstate.xml
108
+
109
+ # Crashlytics plugin (for Android Studio and IntelliJ)
110
+ com_crashlytics_export_strings.xml
111
+ crashlytics.properties
112
+ crashlytics-build.properties
113
+ fabric.properties
114
+
115
+ ### PyCharm Patch ###
116
+ # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
117
+
118
+ # *.iml
119
+ # modules.xml
120
+ # .idea/misc.xml
121
+ # *.ipr
122
+
123
+ # Sonarlint plugin
124
+ .idea/sonarlint
125
+
126
+ ### Python ###
127
+ # Byte-compiled / optimized / DLL files
128
+ *.py[cod]
129
+ *$py.class
130
+
131
+ # C extensions
132
+ *.so
133
+
134
+ # Distribution / packaging
135
+ .Python
136
+ env/
137
+ build/
138
+ develop-eggs/
139
+ dist/
140
+ downloads/
141
+ eggs/
142
+ .eggs/
143
+ lib/
144
+ lib64/
145
+ parts/
146
+ sdist/
147
+ var/
148
+ wheels/
149
+ *.egg-info/
150
+ .installed.cfg
151
+ *.egg
152
+
153
+ # PyInstaller
154
+ # Usually these files are written by a python script from a template
155
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
156
+ *.manifest
157
+ *.spec
158
+
159
+ # Installer logs
160
+ pip-log.txt
161
+ pip-delete-this-directory.txt
162
+
163
+ # Unit test / coverage reports
164
+ htmlcov/
165
+ .tox/
166
+ .coverage
167
+ .coverage.*
168
+ .cache
169
+ nosetests.xml
170
+ coverage.xml
171
+ *,cover
172
+ .hypothesis/
173
+
174
+ # Translations
175
+ *.mo
176
+
177
+ # Django stuff:
178
+
179
+ # Flask stuff:
180
+ instance/
181
+ .webassets-cache
182
+
183
+ # Scrapy stuff:
184
+ .scrapy
185
+
186
+ # Sphinx documentation
187
+ docs/_build/
188
+
189
+ # PyBuilder
190
+ target/
191
+
192
+ # Jupyter Notebook
193
+ .ipynb_checkpoints
194
+
195
+ # pyenv
196
+ .python-version
197
+
198
+ # celery beat schedule file
199
+ celerybeat-schedule
200
+
201
+ # SageMath parsed files
202
+ *.sage.py
203
+
204
+ # dotenv
205
+ .env
206
+ *.env
207
+
208
+ # virtualenv
209
+ .venv
210
+ venv/
211
+ ENV/
212
+
213
+ # Spyder project settings
214
+ .spyderproject
215
+ .spyproject
216
+
217
+ # Rope project settings
218
+ .ropeproject
219
+
220
+ # mkdocs documentation
221
+ /site
222
+
223
+ # End of https://www.gitignore.io/api/macos,linux,django,python,pycharm
README.md CHANGED
@@ -1,12 +1,29 @@
1
  ---
2
- title: Textgames
3
- emoji: 📉
4
- colorFrom: blue
5
- colorTo: red
6
  sdk: gradio
7
- sdk_version: 5.12.0
8
- app_file: app.py
9
- pinned: false
10
  ---
 
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: textgames
3
+ app_file: play_gradio.py
 
 
4
  sdk: gradio
5
+ sdk_version: 5.8.0
 
 
6
  ---
7
+ # TextGames
8
 
9
+ ## Setup
10
+ ```
11
+ ❱❱❱ pip install -r requirements.txt
12
+ ```
13
+
14
+ ## Play (Terminal)
15
+ ```
16
+ ❱❱❱ python play.py
17
+ ```
18
+
19
+ ## Play (Web UI)
20
+ ```
21
+ ❱❱❱ pip install gradio
22
+ ❱❱❱ GRADIO_SERVER_PORT=1080 python play_gradio.py
23
+ ```
24
+ Open `localhost:1080` to access.
25
+
26
+ ## Optional Environment Varibles
27
+ ```
28
+ TEXTGAMES_SHOW_HIDDEN_LEVEL=1
29
+ ```
Try Gemma-2-9B.ipynb ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nbformat": 4,
3
+ "nbformat_minor": 0,
4
+ "metadata": {
5
+ "colab": {
6
+ "private_outputs": true,
7
+ "provenance": [],
8
+ "authorship_tag": "ABX9TyPmvDoFpmwAf1QFBJZy7XSQ"
9
+ },
10
+ "kernelspec": {
11
+ "name": "python3",
12
+ "display_name": "Python 3"
13
+ },
14
+ "language_info": {
15
+ "name": "python"
16
+ }
17
+ },
18
+ "cells": [
19
+ {
20
+ "cell_type": "code",
21
+ "execution_count": null,
22
+ "metadata": {
23
+ "id": "Rli_enT6lBDT"
24
+ },
25
+ "outputs": [],
26
+ "source": [
27
+ "##%%\n",
28
+ "import os\n",
29
+ "import torch\n",
30
+ "import random\n",
31
+ "import numpy as np\n",
32
+ "import argparse\n",
33
+ "import json\n",
34
+ "import cohere\n",
35
+ "from openai import OpenAI\n"
36
+ ]
37
+ },
38
+ {
39
+ "cell_type": "code",
40
+ "source": [
41
+ "##%%\n",
42
+ "from tqdm import tqdm\n",
43
+ "\n",
44
+ "from collections import Counter\n",
45
+ "\n",
46
+ "from transformers import LlamaForCausalLM, AutoTokenizer, AutoModelForCausalLM, AutoModelForSeq2SeqLM\n",
47
+ "import hashlib\n",
48
+ "\n",
49
+ "from textgames import GAME_NAMES, GAME_IDS, LEVELS, LEVELS_HIDDEN, LEVEL_IDS, new_game\n"
50
+ ],
51
+ "metadata": {
52
+ "id": "dp1F32B8oSfD"
53
+ },
54
+ "execution_count": null,
55
+ "outputs": []
56
+ },
57
+ {
58
+ "cell_type": "code",
59
+ "source": [
60
+ "##%%\n",
61
+ "gen_model_checkpoint = \"google/gemma-2-9b-it\"\n",
62
+ "quantize = True"
63
+ ],
64
+ "metadata": {
65
+ "id": "jZF8bkUcojTX"
66
+ },
67
+ "execution_count": null,
68
+ "outputs": []
69
+ },
70
+ {
71
+ "cell_type": "code",
72
+ "source": [
73
+ "kwargs = {\n",
74
+ " \"device_map\": \"auto\",\n",
75
+ "} if quantize else {}"
76
+ ],
77
+ "metadata": {
78
+ "id": "VAF5sR9arYzS"
79
+ },
80
+ "execution_count": null,
81
+ "outputs": []
82
+ },
83
+ {
84
+ "cell_type": "code",
85
+ "source": [
86
+ "##%%\n",
87
+ "gen_model = AutoModelForCausalLM.from_pretrained(gen_model_checkpoint, **kwargs)\n",
88
+ "tokenizer = AutoTokenizer.from_pretrained(gen_model_checkpoint, **kwargs)"
89
+ ],
90
+ "metadata": {
91
+ "id": "tzqldl8ooRVL"
92
+ },
93
+ "execution_count": null,
94
+ "outputs": []
95
+ },
96
+ {
97
+ "cell_type": "code",
98
+ "source": [
99
+ "gen_model.device"
100
+ ],
101
+ "metadata": {
102
+ "id": "FeBUXdkWsWrL"
103
+ },
104
+ "execution_count": null,
105
+ "outputs": []
106
+ },
107
+ {
108
+ "cell_type": "code",
109
+ "source": [
110
+ "def get_gemma_response(text):\n",
111
+ " # global gen_model, tokenizer\n",
112
+ " messages = [\n",
113
+ " {\"role\": \"user\", \"content\": text},\n",
114
+ " ]\n",
115
+ "\n",
116
+ " input_ids = tokenizer.apply_chat_template(\n",
117
+ " messages,\n",
118
+ " add_generation_prompt=True,\n",
119
+ " return_tensors=\"pt\"\n",
120
+ " ).to(gen_model.device)\n",
121
+ "\n",
122
+ " terminators = [\n",
123
+ " tokenizer.eos_token_id,\n",
124
+ " tokenizer.convert_tokens_to_ids(\"<|eot_id|>\")\n",
125
+ " ]\n",
126
+ "\n",
127
+ " outputs = gen_model.generate(\n",
128
+ " input_ids,\n",
129
+ " max_new_tokens=100,\n",
130
+ " eos_token_id=terminators,\n",
131
+ " do_sample=True,\n",
132
+ " temperature=0.2,\n",
133
+ " top_p=1\n",
134
+ " )\n",
135
+ "\n",
136
+ " response = outputs[0][input_ids.shape[-1]:]\n",
137
+ " return tokenizer.decode(response, skip_special_tokens=True)"
138
+ ],
139
+ "metadata": {
140
+ "id": "R5D4K-P2sPaj"
141
+ },
142
+ "execution_count": null,
143
+ "outputs": []
144
+ },
145
+ {
146
+ "cell_type": "code",
147
+ "source": [
148
+ "text = \\\n",
149
+ "\"\"\"\n",
150
+ "Given a set of rules to calculate point, sort the set of words in decreasing order.\n",
151
+ "When there 2 or more words with same point, sort lexicographically.\n",
152
+ "\n",
153
+ "Rules:\n",
154
+ "- every pair of consecutive consonant gets 5 points\n",
155
+ "- every pair of consecutive vowel gets 3 points\n",
156
+ "- add 1 point if there exists exactly 1 'g' in the word\n",
157
+ "- word less than 5 characters gets 10 points\n",
158
+ "- word starts with gen gets 100 points\n",
159
+ "- word ends with ta gets -1000 point\n",
160
+ "\n",
161
+ "Words:\n",
162
+ "- genta\n",
163
+ "- winata\n",
164
+ "- hudi\n",
165
+ "- alham\n",
166
+ "- aji\n",
167
+ "- ruochen\n",
168
+ "\n",
169
+ "Print only the answer.\n",
170
+ "\"\"\"\n",
171
+ "\n",
172
+ "# Answer:\n",
173
+ "# - aji 10\n",
174
+ "# - hudi 10\n",
175
+ "# - ruochen 5 3\n",
176
+ "# - alham 5\n",
177
+ "# - genta 5 1 100 -1000\n",
178
+ "# - winata -1000"
179
+ ],
180
+ "metadata": {
181
+ "id": "T_tk4hTGsxsR"
182
+ },
183
+ "execution_count": null,
184
+ "outputs": []
185
+ },
186
+ {
187
+ "cell_type": "code",
188
+ "source": [
189
+ "print(get_gemma_response(text))"
190
+ ],
191
+ "metadata": {
192
+ "id": "05OI36v6vGoY"
193
+ },
194
+ "execution_count": null,
195
+ "outputs": []
196
+ },
197
+ {
198
+ "cell_type": "code",
199
+ "source": [
200
+ "print(get_gemma_response(text))"
201
+ ],
202
+ "metadata": {
203
+ "id": "riwXqTc-tmNr"
204
+ },
205
+ "execution_count": null,
206
+ "outputs": []
207
+ },
208
+ {
209
+ "cell_type": "code",
210
+ "source": [],
211
+ "metadata": {
212
+ "id": "T72sUG4_vYUa"
213
+ },
214
+ "execution_count": null,
215
+ "outputs": []
216
+ }
217
+ ]
218
+ }
__init__.py ADDED
File without changes
agent.py ADDED
File without changes
environment.yml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # conda env create -f <this_file>
2
+ name: textgame
3
+ channels:
4
+ - conda-forge
5
+ dependencies:
6
+ - python=3.11
7
+ - pip
8
+ - pip:
9
+ - -r requirements.txt
gdrive_try.ipynb ADDED
@@ -0,0 +1 @@
 
 
1
+ {"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"provenance":[]},"kernelspec":{"name":"python3","display_name":"Python 3"},"language_info":{"name":"python"}},"cells":[{"cell_type":"code","source":["# %load_ext autoreload\n","# %autoreload 2"],"metadata":{"id":"mAaspsfqKYkA"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["# %load_ext gradio"],"metadata":{"id":"8fBrlr18CB3F"},"execution_count":null,"outputs":[]},{"cell_type":"code","execution_count":null,"metadata":{"id":"6l8QdIpAB5j_"},"outputs":[],"source":["# %%blocks\n","\n","# from play_gradio import demo"]},{"cell_type":"markdown","source":["---"],"metadata":{"id":"2KNPZna6wl7f"}},{"cell_type":"code","source":["# !pip install -U google-api-python-client google-auth-httplib2 google-auth-oauthlib"],"metadata":{"id":"ExtfLnlMprwD"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["import os.path\n","\n","from google.auth.transport.requests import Request\n","from google.oauth2.credentials import Credentials\n","from google_auth_oauthlib.flow import InstalledAppFlow\n","from googleapiclient.discovery import build\n","from googleapiclient.errors import HttpError"],"metadata":{"id":"ADy9dN7bo_TC"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["# If modifying these scopes, delete the file token.json.\n","SCOPES = [\"https://www.googleapis.com/auth/drive.metadata.readonly\"]"],"metadata":{"id":"-1BrLvGaWxZ2"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["def main():\n"," \"\"\"Shows basic usage of the Drive v3 API.\n"," Prints the names and ids of the first 10 files the user has access to.\n"," \"\"\"\n"," creds = None\n"," # The file token.json stores the user's access and refresh tokens, and is\n"," # created automatically when the authorization flow completes for the first\n"," # time.\n"," if os.path.exists(\"token.json\"):\n"," creds = Credentials.from_authorized_user_file(\"token.json\", SCOPES)\n"," # If there are no (valid) credentials available, let the user log in.\n"," if not creds or not creds.valid:\n"," if creds and creds.expired and creds.refresh_token:\n"," creds.refresh(Request())\n"," else:\n"," flow = InstalledAppFlow.from_client_secrets_file(\n"," \"credentials.json\", SCOPES\n"," )\n"," creds = flow.run_local_server(port=0)\n"," # Save the credentials for the next run\n"," with open(\"token.json\", \"w\") as token:\n"," token.write(creds.to_json())\n","\n"," try:\n"," service = build(\"drive\", \"v3\", credentials=creds)\n","\n"," # Call the Drive v3 API\n"," results = (\n"," service.files()\n"," .list(pageSize=10, fields=\"nextPageToken, files(id, name)\")\n"," .execute()\n"," )\n"," items = results.get(\"files\", [])\n","\n"," if not items:\n"," print(\"No files found.\")\n"," return\n"," print(\"Files:\")\n"," for item in items:\n"," print(f\"{item['name']} ({item['id']})\")\n"," except HttpError as error:\n"," # TODO(developer) - Handle errors from drive API.\n"," print(f\"An error occurred: {error}\")"],"metadata":{"id":"-qwFweRKqBZ0"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["main()"],"metadata":{"id":"x62A2GyFqH19"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["if __name__ == \"__main__\":\n"," main()"],"metadata":{"id":"6mwHQ-vFqF-y"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":[],"metadata":{"id":"xqVim7U8qBRn"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":[],"metadata":{"id":"KrIr5DqVwFPR"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["import google.auth\n","from googleapiclient.discovery import build\n","from googleapiclient.errors import HttpError\n","from googleapiclient.http import MediaFileUpload"],"metadata":{"id":"bKn3EWoHtIw3"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["creds, _ = google.auth.default()"],"metadata":{"id":"siH9BRzptIhS"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["creds"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"Bhz_xW99tLDS","executionInfo":{"status":"ok","timestamp":1736790633770,"user_tz":-540,"elapsed":8,"user":{"displayName":"Frederikus Hudi","userId":"06823040510862360282"}},"outputId":"3271b64c-82cd-466f-8321-b5d0ddc884d6"},"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"text/plain":["<google.oauth2.service_account.Credentials at 0x108b81d90>"]},"metadata":{},"execution_count":3}]},{"cell_type":"code","source":["service = build(\"drive\", \"v3\", credentials=creds)"],"metadata":{"id":"Z3UPsS57tIaP"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["service"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"FhPuaL29ziYw","executionInfo":{"status":"ok","timestamp":1736791252169,"user_tz":-540,"elapsed":9,"user":{"displayName":"Frederikus Hudi","userId":"06823040510862360282"}},"outputId":"66e7a320-3133-4647-9a73-94b4f65ca898"},"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"text/plain":["<googleapiclient.discovery.Resource at 0x108b8f550>"]},"metadata":{},"execution_count":11}]},{"cell_type":"code","source":["file_metadata = {\"name\": \"wow.txt\"}\n","media = MediaFileUpload(\"wow.txt\")"],"metadata":{"id":"sckvnGYtqBKw"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["media"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"xjvE-Mva03L0","executionInfo":{"status":"ok","timestamp":1736791259359,"user_tz":-540,"elapsed":9,"user":{"displayName":"Frederikus Hudi","userId":"06823040510862360282"}},"outputId":"5eac30fc-1da6-48c3-93ca-40e0174d1816"},"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"text/plain":["<googleapiclient.http.MediaFileUpload at 0x108c223d0>"]},"metadata":{},"execution_count":12}]},{"cell_type":"code","source":["dir(media)"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"qK-N7udo7u1i","executionInfo":{"status":"ok","timestamp":1736793093612,"user_tz":-540,"elapsed":27,"user":{"displayName":"Frederikus Hudi","userId":"06823040510862360282"}},"outputId":"a961fd11-4fae-4473-94ab-9bc775a6335e"},"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"text/plain":["['__class__',\n"," '__del__',\n"," '__delattr__',\n"," '__dict__',\n"," '__dir__',\n"," '__doc__',\n"," '__eq__',\n"," '__format__',\n"," '__ge__',\n"," '__getattribute__',\n"," '__getstate__',\n"," '__gt__',\n"," '__hash__',\n"," '__init__',\n"," '__init_subclass__',\n"," '__le__',\n"," '__lt__',\n"," '__module__',\n"," '__ne__',\n"," '__new__',\n"," '__reduce__',\n"," '__reduce_ex__',\n"," '__repr__',\n"," '__setattr__',\n"," '__sizeof__',\n"," '__str__',\n"," '__subclasshook__',\n"," '__weakref__',\n"," '_chunksize',\n"," '_fd',\n"," '_filename',\n"," '_mimetype',\n"," '_resumable',\n"," '_size',\n"," '_to_json',\n"," 'chunksize',\n"," 'from_json',\n"," 'getbytes',\n"," 'has_stream',\n"," 'mimetype',\n"," 'new_from_json',\n"," 'resumable',\n"," 'size',\n"," 'stream',\n"," 'to_json']"]},"metadata":{},"execution_count":49}]},{"cell_type":"code","source":["f = (\n"," service.files()\n"," .create(body=file_metadata, media_body=media, fields=\"id\")\n"," .execute()\n"," )\n","print(f'File ID: {file.get(\"id\")}')"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"HipJX087zir2","executionInfo":{"status":"ok","timestamp":1736792615979,"user_tz":-540,"elapsed":2189,"user":{"displayName":"Frederikus Hudi","userId":"06823040510862360282"}},"outputId":"19a11b14-cc18-455a-a4f1-d1a69f69c72f"},"execution_count":null,"outputs":[{"output_type":"stream","name":"stdout","text":["File ID: 1Rnx0_wttlDG8XiyA_44yHBJeQBHvR_xh\n"]}]},{"cell_type":"code","source":["f.get(\"id\"), type(f)"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"GoMMo7qX5-OH","executionInfo":{"status":"ok","timestamp":1736792629838,"user_tz":-540,"elapsed":10,"user":{"displayName":"Frederikus Hudi","userId":"06823040510862360282"}},"outputId":"ade9f343-a514-4e61-97f9-2550ed55223c"},"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"text/plain":["('1rE4uYBWAQBFVVB6m7jGqAJncIohTJrWp', dict)"]},"metadata":{},"execution_count":22}]},{"cell_type":"code","source":["f = file.get(\"id\")"],"metadata":{"id":"usWmDGQjzijj"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["f"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"PFCzbG0v0nZ-","executionInfo":{"status":"ok","timestamp":1736791193891,"user_tz":-540,"elapsed":4,"user":{"displayName":"Frederikus Hudi","userId":"06823040510862360282"}},"outputId":"9c8456bd-9996-40a4-e3e6-1e3eb0aafbe9"},"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"text/plain":["'1Rnx0_wttlDG8XiyA_44yHBJeQBHvR_xh'"]},"metadata":{},"execution_count":9}]},{"cell_type":"code","source":["files = service.files()"],"metadata":{"id":"oh18pUBj3Hn4"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["dir(service)"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"LRsVc6pQ6c_1","executionInfo":{"status":"ok","timestamp":1736792725586,"user_tz":-540,"elapsed":4,"user":{"displayName":"Frederikus Hudi","userId":"06823040510862360282"}},"outputId":"c9ba40eb-f159-4f05-f5b1-53e5e81916da"},"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"text/plain":["['__class__',\n"," '__delattr__',\n"," '__dict__',\n"," '__dir__',\n"," '__doc__',\n"," '__enter__',\n"," '__eq__',\n"," '__exit__',\n"," '__format__',\n"," '__ge__',\n"," '__getattribute__',\n"," '__getstate__',\n"," '__gt__',\n"," '__hash__',\n"," '__init__',\n"," '__init_subclass__',\n"," '__le__',\n"," '__lt__',\n"," '__module__',\n"," '__ne__',\n"," '__new__',\n"," '__reduce__',\n"," '__reduce_ex__',\n"," '__repr__',\n"," '__setattr__',\n"," '__setstate__',\n"," '__sizeof__',\n"," '__str__',\n"," '__subclasshook__',\n"," '__weakref__',\n"," '_add_basic_methods',\n"," '_add_nested_resources',\n"," '_add_next_methods',\n"," '_baseUrl',\n"," '_credentials_validated',\n"," '_developerKey',\n"," '_dynamic_attrs',\n"," '_http',\n"," '_model',\n"," '_requestBuilder',\n"," '_resourceDesc',\n"," '_rootDesc',\n"," '_schema',\n"," '_set_dynamic_attr',\n"," '_set_service_methods',\n"," '_universe_domain',\n"," '_validate_credentials',\n"," 'about',\n"," 'accessproposals',\n"," 'apps',\n"," 'changes',\n"," 'channels',\n"," 'close',\n"," 'comments',\n"," 'drives',\n"," 'files',\n"," 'new_batch_http_request',\n"," 'operation',\n"," 'operations',\n"," 'permissions',\n"," 'replies',\n"," 'revisions',\n"," 'teamdrives']"]},"metadata":{},"execution_count":31}]},{"cell_type":"code","source":["dir(service.files())"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"40nai28V7Q9B","executionInfo":{"status":"ok","timestamp":1736792941319,"user_tz":-540,"elapsed":14,"user":{"displayName":"Frederikus Hudi","userId":"06823040510862360282"}},"outputId":"4e9488bc-9370-44a3-c5cc-8d009630dc2c"},"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"text/plain":["['__class__',\n"," '__delattr__',\n"," '__dict__',\n"," '__dir__',\n"," '__doc__',\n"," '__enter__',\n"," '__eq__',\n"," '__exit__',\n"," '__format__',\n"," '__ge__',\n"," '__getattribute__',\n"," '__getstate__',\n"," '__gt__',\n"," '__hash__',\n"," '__init__',\n"," '__init_subclass__',\n"," '__le__',\n"," '__lt__',\n"," '__module__',\n"," '__ne__',\n"," '__new__',\n"," '__reduce__',\n"," '__reduce_ex__',\n"," '__repr__',\n"," '__setattr__',\n"," '__setstate__',\n"," '__sizeof__',\n"," '__str__',\n"," '__subclasshook__',\n"," '__weakref__',\n"," '_add_basic_methods',\n"," '_add_nested_resources',\n"," '_add_next_methods',\n"," '_baseUrl',\n"," '_credentials_validated',\n"," '_developerKey',\n"," '_dynamic_attrs',\n"," '_http',\n"," '_model',\n"," '_requestBuilder',\n"," '_resourceDesc',\n"," '_rootDesc',\n"," '_schema',\n"," '_set_dynamic_attr',\n"," '_set_service_methods',\n"," '_universe_domain',\n"," '_validate_credentials',\n"," 'close',\n"," 'copy',\n"," 'create',\n"," 'delete',\n"," 'download',\n"," 'emptyTrash',\n"," 'export',\n"," 'export_media',\n"," 'generateIds',\n"," 'get',\n"," 'get_media',\n"," 'list',\n"," 'listLabels',\n"," 'listLabels_next',\n"," 'list_next',\n"," 'modifyLabels',\n"," 'update',\n"," 'watch']"]},"metadata":{},"execution_count":34}]},{"cell_type":"code","source":["files.delete(fileId=\"1Rnx0_wttlDG8XiyA_44yHBJeQBHvR_xh\").execute()"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"9zlanjZ96Yz9","executionInfo":{"status":"ok","timestamp":1736793000575,"user_tz":-540,"elapsed":896,"user":{"displayName":"Frederikus Hudi","userId":"06823040510862360282"}},"outputId":"d6ed9310-2efb-48d1-90e5-d5b0bd9536f6"},"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"text/plain":["''"]},"metadata":{},"execution_count":40}]},{"cell_type":"code","source":["files.list().execute()"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"4guAyYqt7ehH","executionInfo":{"status":"ok","timestamp":1736793001570,"user_tz":-540,"elapsed":441,"user":{"displayName":"Frederikus Hudi","userId":"06823040510862360282"}},"outputId":"d96fe0c6-71b7-4bc3-8add-afbff7bbb054"},"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"text/plain":["{'kind': 'drive#fileList',\n"," 'incompleteSearch': False,\n"," 'files': [{'kind': 'drive#file',\n"," 'mimeType': 'application/vnd.google-apps.folder',\n"," 'id': '1qStKuVerAQPsXagngfzlNg8PdAR5hupA',\n"," 'name': 'afureteshimau'}]}"]},"metadata":{},"execution_count":41}]},{"cell_type":"code","source":["ret = (\n"," files\n"," .create(body=, media_body=media)\n"," .execute()\n",")"],"metadata":{"id":"qS4ithoH7lBq"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["results = (\n"," service.files()\n"," .list(pageSize=10, fields=\"nextPageToken, files(id, name)\")\n"," .execute()\n"," )"],"metadata":{"id":"DJQwlCAq1IDz"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":["results.get(\"files\", [])"],"metadata":{"id":"kKSZI82R1J2s"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":[],"metadata":{"id":"qED1Eo_zKI4o"},"execution_count":null,"outputs":[]}]}
generate.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import numpy as np
4
+ import torch
5
+ import json
6
+
7
+ from tqdm import tqdm
8
+ from pathlib import Path
9
+ from textgames import GAME_NAMES, LEVEL_IDS, new_game, game_filename
10
+
11
+
12
+ def set_seed(seed):
13
+ random.seed(seed)
14
+ np.random.seed(seed)
15
+ torch.manual_seed(seed)
16
+ torch.cuda.manual_seed(seed)
17
+
18
+
19
+ #generate()
20
+ if __name__ == '__main__':
21
+ outdir = Path(os.getenv("TEXTGAMES_LOADGAME_DIR", "problemsets"))
22
+ # os.system(f"rm -rfd {outdir}")
23
+ os.makedirs(outdir, exist_ok=False) # exists_ok is set to False, making sure regeneration.
24
+ set_seed(42)
25
+
26
+ # level_ids = LEVEL_IDS
27
+ level_ids = ["1", "2", "3"]
28
+ session_ids = [
29
+ f"session_{sid:04}" for sid in range(os.getenv("TEXTGAMES_GENERATE_N", 1000))
30
+ ]
31
+
32
+ count_duplicate = 0
33
+ for game_name in GAME_NAMES:
34
+ prompts_map = dict()
35
+ for level_id in level_ids:
36
+ os.environ["TEXTGAMES_NEWGAME_ERRFILE"] = f"{outdir}/{game_filename(game_name)}_{level_id}.err"
37
+ for sid in tqdm(session_ids, desc=f"{game_name}_{level_id}"):
38
+ while True:
39
+ cur_game = new_game(game_name, level_id)
40
+ prompt = cur_game._get_prompt()
41
+ if prompt not in prompts_map:
42
+ break
43
+ count_duplicate += 1
44
+ prompts_map[prompt] = sid
45
+ print(f"[{game_name}_{level_id}] Duplicate #: {count_duplicate:-4}")
46
+
47
+ json_object = json.dumps({sid: prompt for prompt, sid in prompts_map.items()}, indent=4)
48
+ with open(outdir / f"{game_filename(game_name)}_{level_id}.json", "w") as outfile:
49
+ outfile.write(json_object)
50
+
51
+ print(f"duplicates:{count_duplicate}")
model_outputs/results_batch_api_openai_gpt-4o-mini-2024-07-18.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
oauth_environ_google.sh ADDED
@@ -0,0 +1 @@
 
 
1
+ export $(cat oauth_environ_google.env | xargs)
play.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from termcolor import colored
2
+ from textgames import GAME_IDS, GAME_NAMES, LEVEL_IDS, LEVELS, new_game, LEVELS_HIDDEN, SINGLE_LINE_GAME_IDS
3
+
4
+ import os
5
+
6
+
7
+ def print_text_green(string):
8
+ print(colored(string, "light_green"))
9
+
10
+ def print_text_cyan(string):
11
+ print(colored(string, "cyan"))
12
+
13
+ def print_text_white(string):
14
+ print(colored(string, "white"))
15
+
16
+
17
+ if __name__ == "__main__":
18
+ print_text_green("#" * 20)
19
+ print_text_cyan(" Welcome to")
20
+ print(" 🎮 " + colored("Text", "white")+ colored("Games", "yellow"))
21
+ print_text_green("#" * 20)
22
+ print_text_green("Games:")
23
+ for i, game_name in zip(range(len(GAME_NAMES)), GAME_NAMES):
24
+ print_text_green(f"{i+1}. {game_name}")
25
+ print_text_green("#" * 20)
26
+
27
+ cur_game_id = os.getenv("GAME_ID", None)
28
+ difficulty_level = os.getenv("GAME_LEVEL", None)
29
+ while cur_game_id is None:
30
+ user_input = str(input(f"Choose the game> "))
31
+
32
+ if user_input in GAME_IDS:
33
+ cur_game_id = user_input
34
+
35
+ print_text_green("#" * 20)
36
+ print_text_green("Difficulty Levels:")
37
+ for i, l in zip(LEVEL_IDS, LEVELS):
38
+ print_text_green(f"{i}. {l}")
39
+ print_text_green("#" * 20)
40
+
41
+ while difficulty_level is None:
42
+ user_input = str(input(f"Choose the difficulty level> "))
43
+ if user_input in LEVEL_IDS:
44
+ difficulty_level = user_input
45
+ else:
46
+ print("The difficulty level option is not available.")
47
+ else:
48
+ arr = user_input.split("-")
49
+ if len(arr) == 2 and isinstance(arr[0], str) and isinstance(arr[1], str):
50
+ if arr[0] in GAME_IDS and arr[1] in LEVEL_IDS:
51
+ cur_game_id = arr[0]
52
+ difficulty_level = arr[1]
53
+ else:
54
+ print("The game option is not available.")
55
+ cur_game = None
56
+
57
+ this_game_name = GAME_NAMES[GAME_IDS.index(cur_game_id)]
58
+ this_difficulty_level = (LEVELS + LEVELS_HIDDEN)[LEVEL_IDS.index(difficulty_level)].replace("\t", " ")
59
+ print_text_green(f"Game chosen: {this_game_name} and Difficulty Level: {this_difficulty_level}")
60
+
61
+ solved = False
62
+ cur_game = new_game(this_game_name, difficulty_level)
63
+ print(colored("######## Game Start !! ########", "light_green"))
64
+ print(cur_game.get_prompt())
65
+ while not solved:
66
+ contents = []
67
+ while True:
68
+ try:
69
+ line = str(input("\t" if contents else f"Guess>\t"))
70
+ # automatic break for some games:
71
+ if cur_game_id in SINGLE_LINE_GAME_IDS:
72
+ contents.append(line)
73
+ break
74
+ if len(line) == 0:
75
+ break
76
+ except EOFError:
77
+ break
78
+ contents.append(line)
79
+
80
+ user_input = '\n'.join(contents)
81
+ solved, val_msg = cur_game.validate(user_input)
82
+ if val_msg:
83
+ print(val_msg)
84
+ print(f"Attempt #{len(cur_game.attempt_timestamps)}:", "Correct guess" if solved else "Bad guess", end="\n\n")
85
+
86
+ print(f"Time to solve: {round(cur_game.attempt_timestamps[-1] - cur_game.start_timestamp, 1)}s")
87
+ print("Thank you for playing!")
88
+
89
+
play.sh ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ cur_datetime="$(date '+%Y%m%d_%H%M%S')";
2
+ echo "Current Time: ${cur_datetime}";
3
+ python -u play_gradio.py | tee "log/run_${cur_datetime}.log"
play_gradio.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #%%
2
+ import os
3
+ # os.environ.setdefault("GRADIO_SERVER_PORT", "1080")
4
+ # os.environ.setdefault("TEXTGAMES_SHOW_HIDDEN_LEVEL", "1")
5
+ os.environ.setdefault("TEXTGAMES_LOADGAME_DIR", "problemsets")
6
+ os.environ.setdefault("TEXTGAMES_LOADGAME_ID", "42")
7
+ os.environ.setdefault("TEXTGAMES_MOCKUSER", "")
8
+ os.environ.setdefault("TEXTGAMES_OUTPUT_DIR", "user_outputs")
9
+ favicon_path = "textgames-scrabble-black2-ss.png"
10
+
11
+ #%%
12
+ from play_helper import css, declare_components, start_new_game, download_from_drive
13
+ import pandas as pd
14
+ import gradio as gr
15
+
16
+
17
+ #%%
18
+ fp_user_auth = f"{os.getenv('TEXTGAMES_OUTPUT_DIR')}/textgames_userauth.tsv"
19
+ # fp_user_auth_id = "13RLyxV3ys5DGgRIJt5_tO-ILllJ1LDPGasobagZyVLU"
20
+ fp_user_auth_mime_type = "text/tab-separated-values"
21
+
22
+
23
+ #%%
24
+ def file_based_auth(username, password):
25
+ if os.getenv('TEXTGAMES_MOCKUSER', ''):
26
+ return True
27
+ download_from_drive(fp_user_auth, mime_type=fp_user_auth_mime_type)
28
+ df_auth = pd.read_csv(fp_user_auth, sep="\t").dropna(how="any")
29
+ return len(df_auth.loc[(df_auth.EMAIL == username) & (df_auth.PASSWORD == password)]) > 0
30
+
31
+
32
+ #%%
33
+ def greet(request: gr.Request):
34
+ email = os.getenv('TEXTGAMES_MOCKUSER', '')
35
+ if email:
36
+ user = {'email': email, 'name': "mockuser"}
37
+ else:
38
+ df_auth = pd.read_csv(fp_user_auth, sep="\t").dropna(how="any").drop_duplicates(subset=['EMAIL'])
39
+ r = df_auth.loc[df_auth.EMAIL == request.username].iloc[0]
40
+ user = {'email': r.EMAIL, 'name': r.NAME}
41
+ return f"""
42
+ Welcome to TextGames, {user['name']}!<br/><{user['email'].replace('@', '{at}')}>
43
+ """, user, user['email']
44
+
45
+ # return f"""
46
+ # Welcome to TextGames, {user['name']}!<br />
47
+ # <{user['email'].replace('@', '{at}')}> ({'' if user['email_verified'] else 'NON-'}verified email)
48
+ # """, None, None
49
+
50
+
51
+ #%%
52
+ with gr.Blocks(title="TextGames", css=css, delete_cache=(3600, 3600)) as demo:
53
+ ((m, logout_btn, solved_games_df, game_radio, level_radio, new_game_btn, render_toggle),
54
+ (session_state, is_solved, solved_games, user_state, uid_state),
55
+ ) = declare_components(demo, greet)
56
+
57
+ @gr.render(inputs=[game_radio, level_radio, user_state, session_state, uid_state], triggers=[render_toggle.change])
58
+ def _start_new_game(game_name, level, user, _session_state, _uid_state):
59
+ if _session_state in [1, 2]:
60
+ start_new_game(game_name, level, session_state, is_solved, solved_games, user=user, uid=_uid_state)
61
+
62
+ demo.launch(
63
+ auth=file_based_auth,
64
+ favicon_path=favicon_path if os.path.exists(favicon_path) else None,
65
+ share=True,
66
+ )
67
+
68
+
69
+ #%%
70
+
71
+
72
+ #%%
73
+
74
+
75
+ #%%
76
+
77
+
play_helper.py CHANGED
@@ -3,11 +3,8 @@ import os
3
  import time
4
  import pandas as pd
5
  import gradio as gr
6
-
7
- import google.auth
8
- from googleapiclient.discovery import build
9
- from googleapiclient.errors import HttpError
10
- from googleapiclient.http import MediaFileUpload
11
 
12
  from textgames import GAME_NAMES, LEVEL_IDS, LEVELS, new_game, preload_game, game_filename
13
  from textgames.islands.islands import Islands
@@ -17,7 +14,14 @@ from textgames.ordering_text.ordering_text import OrderingTextGame
17
 
18
 
19
  # %%
20
- def declare_components():
 
 
 
 
 
 
 
21
  with gr.Row():
22
  with gr.Column(scale=1):
23
  m = gr.Markdown("Welcome to TextGames!", elem_id="md-greeting")
@@ -29,10 +33,44 @@ def declare_components():
29
  level_radio = gr.Radio(LEVELS, label="Level", elem_id="radio-level-name")
30
  new_game_btn = gr.Button("Start Game", elem_id="btn-start-game")
31
  render_toggle = gr.Checkbox(False, visible=False, interactive=False)
32
- return m, logout_btn, solved_games_df, game_radio, level_radio, new_game_btn, render_toggle
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
 
35
  # %%
 
 
 
 
 
36
  _creds_dict = {
37
  "type": "service_account",
38
  "project_id": os.getenv("GOOGLE_AUTH_CREDS_PROJECT_ID", ""),
@@ -51,6 +89,14 @@ _service = build("drive", "v3", credentials=_creds)
51
  _files = _service.files()
52
 
53
 
 
 
 
 
 
 
 
 
54
  # %%
55
  js_remove_input_helper = """(s) => {
56
  var el = document.getElementById('lintao-container');
@@ -84,9 +130,10 @@ function island() {{
84
  container.style.display = 'grid';
85
  container.style.gridTemplateColumns = container.style.gridTemplateRows = `repeat(${{grid_N}}, ${{grid_px}}px)`;
86
  container.style.gap = '1px';
87
- container.style.border = '2px solid black';
88
  container.style.width = 'max-content';
89
  container.style.margin = '5px 0px 5px 40px';
 
90
  container.id = 'lintao-container';
91
 
92
  for (let i = 0; i < grid_N; ++i) {{
@@ -98,7 +145,7 @@ function island() {{
98
  cell.style.alignItems = 'center';
99
  cell.style.justifyContent = 'center';
100
  cell.style.fontSize = `${{grid_px/2}}px`;
101
- cell.style.border = '1px solid gray';
102
  cell.style.cursor = 'pointer';
103
  cell.id = `lintao-cell-${{i}}-${{j}}`;
104
 
@@ -130,6 +177,8 @@ function island() {{
130
 
131
  js_island_submit = """
132
  function island_submit(textarea, io_history) {{
 
 
133
  const grid_N = {N};
134
  var ret = "";
135
  for (let i = 0; i < grid_N; ++i) {{
@@ -149,7 +198,7 @@ function sudoku() {{
149
  const N = {N};
150
  const grid_N = N*N,
151
  grid_px = 50,
152
- border_px = 3;
153
  const mat = {mat};
154
 
155
  let is_numeric_sudoku = false;
@@ -165,16 +214,17 @@ function sudoku() {{
165
  const container = document.createElement('div');
166
  container.style.display = 'grid';
167
  container.style.gridTemplateColumns = container.style.gridTemplateRows = `repeat(${{grid_N}}, ${{grid_px}}px)`;
168
- container.style.gap = '1px';
169
- container.style.border = '${{border_px}}px solid white';
170
  container.style.width = 'max-content';
171
  container.style.margin = '5px 0px 5px 40px';
 
172
  container.id = 'lintao-container';
173
 
174
  // Generate the grid
 
175
  for (let i = 0; i < grid_N; ++i) {{
176
  for (let j = 0; j < grid_N; ++j) {{
177
- const cell = document.createElement('input');
178
  cell.type = 'text';
179
  cell.maxLength = 1;
180
  cell.style.width = cell.style.height = `${{grid_px}}px`;
@@ -183,32 +233,35 @@ function sudoku() {{
183
  cell.style.justifyContent = 'center';
184
  cell.style.textAlign = 'center';
185
  cell.style.fontSize = `${{grid_px/2}}px`;
186
- cell.style.border = '1px solid #c0c0c0';
187
- cell.style.backgroundColor = 'black'
188
- cell.style.cursor = 'pointer';
 
 
189
  cell.id = `lintao-cell-${{i}}-${{j}}`;
190
 
191
  if (mat[i][j] != '_') {{
192
- cell.value = mat[i][j];
193
- cell.style.color = '#D0D0D0'
194
  cell.disabled = true;
 
 
 
195
  }}
196
 
197
- //cell.style.color = 'black';
198
- //cell.style.outline = 'none';
199
 
200
- if (j % N === 0) cell.style.borderLeft = `${{border_px}}px solid white`;
201
- if (j % N === (N-1)) cell.style.borderRight = `${{border_px}}px solid white`;
202
- if (i % N === 0) cell.style.borderTop = `${{border_px}}px solid white`;
203
- if (i % N === (N-1)) cell.style.borderBottom = `${{border_px}}px solid white`;
204
 
205
  // Allow only numbers 1-9 or A-I
206
  cell.addEventListener('input', (e) => {{
207
- if ((N === 2 && (!(is_numeric_sudoku?/^[1-4]$/:/^[A-Da-d]$/).test(e.target.value))) ||
208
- (N === 3 && (!(is_numeric_sudoku?/^[1-9]$/:/^[A-Ia-i]$/).test(e.target.value)))) {{
209
- e.target.value = '';
210
  }}
211
- e.target.value = e.target.value.toUpperCase();
212
  }});
213
 
214
  container.appendChild(cell);
@@ -228,16 +281,16 @@ function sudoku() {{
228
  const currentCol = i % grid_N;
229
 
230
  if (currentRow === row || currentCol === col || (Math.floor(currentRow / N) === Math.floor(row / N) && Math.floor(currentCol / N) === Math.floor(col / N))) {{
231
- cell.style.backgroundColor = '#303039';
232
  }} else {{
233
- cell.style.backgroundColor = 'black';
234
  }}
235
  }}
236
  }});
237
 
238
  container.addEventListener('focusout', () => {{
239
  for (let i = 0; i < grid_N * grid_N; i++) {{
240
- container.children[i].style.backgroundColor = 'black';
241
  }}
242
  }});
243
 
@@ -248,13 +301,15 @@ function sudoku() {{
248
 
249
  js_sudoku_submit = """
250
  function sudoku_submit(textarea, io_history) {{
 
 
251
  const N = {N};
252
  const grid_N = N*N;
253
  var ret = "";
254
  for (let i = 0; i < grid_N; ++i) {{
255
  if (i > 0) ret += '\\n';
256
  for (let j = 0; j < grid_N; ++j) {{
257
- ret += document.getElementById(`lintao-cell-${{i}}-${{j}}`).value;
258
  }}
259
  }}
260
  return [ret, io_history];
@@ -272,9 +327,10 @@ function crossword() {{
272
  container.style.display = 'grid';
273
  container.style.gridTemplateColumns = container.style.gridTemplateRows = `repeat(${{grid_N}}, ${{grid_px}}px)`;
274
  container.style.gap = '1px';
275
- container.style.border = '2px solid white';
276
  container.style.width = 'max-content';
277
  container.style.margin = '5px 0px 5px 40px';
 
278
  container.id = 'lintao-container';
279
 
280
  // Generate the grid
@@ -290,8 +346,8 @@ function crossword() {{
290
  cell.style.justifyContent = 'center';
291
  cell.style.textAlign = 'center';
292
  cell.style.fontSize = `${{grid_px/2}}px`;
293
- cell.style.border = '1px solid #c0c0c0';
294
- cell.style.backgroundColor = 'black'
295
  cell.style.cursor = 'pointer';
296
  cell.id = `lintao-cell-${{i}}-${{j}}`;
297
 
@@ -313,6 +369,8 @@ function crossword() {{
313
 
314
  js_crossword_submit = """
315
  function crossword_submit(textarea, io_history) {{
 
 
316
  const grid_N = {N};
317
  var ret = "";
318
  for (let i = 0; i < grid_N; ++i) {{
@@ -333,7 +391,7 @@ function ordering() {{
333
  listContainer.style.listStyle = 'none';
334
  listContainer.style.padding = '0';
335
  listContainer.style.width = '20em';
336
- listContainer.style.border = '2px solid white';
337
  listContainer.style.margin = '5px 0px 5px 40px';
338
  listContainer.id = 'lintao-container';
339
 
@@ -346,9 +404,9 @@ function ordering() {{
346
  listItem.textContent = itemText;
347
  listItem.draggable = true;
348
  listItem.style.padding = '10px';
349
- listItem.style.border = '1px solid #c0c0c0';
350
  listItem.style.margin = '3px';
351
- listItem.style.backgroundColor = 'black';
352
  listItem.style.cursor = 'grab';
353
  listItem.id = `lintao-item-${{index}}`;
354
 
@@ -356,16 +414,16 @@ function ordering() {{
356
  listItem.addEventListener('dragstart', (e) => {{
357
  const draggedIndex = Array.from(listContainer.children).indexOf(listItem);
358
  e.dataTransfer.setData('text/plain', draggedIndex);
359
- listItem.style.backgroundColor = '#1f1811';
360
  }});
361
 
362
  listItem.addEventListener('dragover', (e) => {{
363
  e.preventDefault();
364
- listItem.style.backgroundColor = '#303030';
365
  }});
366
 
367
  listItem.addEventListener('dragleave', () => {{
368
- listItem.style.backgroundColor = 'black';
369
  }});
370
 
371
  listItem.addEventListener('drop', (e) => {{
@@ -379,11 +437,11 @@ function ordering() {{
379
  listContainer.insertBefore(draggedItem, targetIndex > draggedIndex ? listItem.nextSibling : listItem);
380
  }}
381
 
382
- listItem.style.backgroundColor = 'black';
383
  }});
384
 
385
  listItem.addEventListener('dragend', () => {{
386
- listItem.style.backgroundColor = 'black';
387
  }});
388
 
389
  listContainer.appendChild(listItem);
@@ -396,9 +454,10 @@ function ordering() {{
396
 
397
  js_ordering_submit = """
398
  function ordering_submit(textarea, io_history) {{
 
 
399
  var ret = "";
400
- const container =
401
- document.getElementById("lintao-container").childNodes.forEach(
402
  (c, i) => {{
403
  if (i>0) ret += '\\n';
404
  ret += c.textContent;
@@ -424,6 +483,68 @@ def _get_file_output(game_name, level_id, fn_prefix):
424
  return f"{fd}/{fn_prefix}_-_{game_filename(game_name)}_{level_id}.pkl"
425
 
426
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  # %%
428
  def start_new_game(game_name, level, session_state_component, is_solved_component, solved_games_component,
429
  user=None, show_timer=False, uid=None):
@@ -441,9 +562,12 @@ def start_new_game(game_name, level, session_state_component, is_solved_componen
441
  preload_game(game_name, difficulty_level, user)
442
  )
443
  cur_game.attach_stats_output_(fp_out)
444
- cur_game.flush_stats_(user=user)
445
 
446
  def add_msg(new_msg, prev_msg):
 
 
 
447
  user_input = '\n'.join(new_msg.split())
448
  solved, val_msg = cur_game.validate(user_input)
449
  response = ("Correct guess" if solved else "Bad guess (Wrong Answer)") + "\n" + val_msg
@@ -455,10 +579,8 @@ def start_new_game(game_name, level, session_state_component, is_solved_componen
455
 
456
  gr.Markdown(
457
  """
458
- > ### ‼️ Do ***NOT*** refresh this page. ‼️<br>
459
  > #### ⚠️ Refreshing the page equals "Give-up 😭" ⚠️
460
-
461
-
462
  """
463
  )
464
  showhide_helper_btn = gr.Button("Show Input Helper (disabling manual input)", elem_id="lintao-helper-btn")
@@ -500,48 +622,52 @@ def start_new_game(game_name, level, session_state_component, is_solved_componen
500
 
501
  def _forfeiting(confirmed, _solved_games):
502
  if confirmed:
 
503
  cur_game.finish_stats_(forfeit=True)
504
- if level in LEVELS[1:4] and level not in _solved_games[game_name]:
505
  _solved_games[game_name].append(level)
 
506
  return 0, _solved_games
507
  return 1, _solved_games
508
- give_up_checkbox.change(_forfeiting, [give_up_checkbox, solved_games_component],
509
- [session_state_component, solved_games_component])
 
 
 
 
 
510
 
511
- def game_is_solved(_is_solved, _session_state, _solved_games):
512
  if _is_solved:
513
- if level in LEVELS[1:4] and level not in _solved_games[game_name]:
514
  _solved_games[game_name].append(level)
515
  return (
516
  2,
517
  gr.update(visible=False, interactive=False),
518
  gr.update(visible=False, interactive=False),
519
- gr.update(visible=True, interactive=True),
520
  _solved_games,
 
521
  )
522
  else:
523
  return (
524
- _session_state, gr.update(), gr.update(), gr.update(), _solved_games
525
  )
526
 
527
- def upload_to_drive():
528
- fn = fp_out.rsplit("/", 1)[-1]
529
- file_metadata = {"name": fn, "parents": ["1qStKuVerAQPsXagngfzlNg8PdAR5hupA"]}
530
- media = MediaFileUpload(fp_out)
531
- # print(f"{file_metadata}\n{fn}")
532
- try:
533
- _files.create(body=file_metadata, media_body=media).execute()
534
- except HttpError as error:
535
- print(f"An error occurred: {error}")
536
 
537
  is_solved_component.change(
538
  game_is_solved,
539
  [is_solved_component, session_state_component, solved_games_component],
540
- [session_state_component, submit_btn, give_up_btn, finish_btn, solved_games_component],
 
 
541
  )
542
  finish_btn.click(
543
- upload_to_drive, None, None,
544
- ).then(
545
  lambda: (0, 0), None, [session_state_component, is_solved_component]
546
  )
547
 
@@ -556,24 +682,34 @@ def check_to_start_new_game(game_name, level, user=None, uid=None):
556
  raise gr.Error(f"You have done this game already.<br/>{game_name} - {level}")
557
  if user is None:
558
  gr.Warning("no user, game will be generated randomly")
559
- else:
560
- if not user['email_verified']:
561
- gr.Warning("please verify your email address")
562
- elif user['email_verified'] == "mockuser":
563
- gr.Info("game will load with a mocked-up user")
564
  return 1
565
 
566
 
567
  # %%
568
- def check_played_game(solved_games, uid):
 
 
 
 
569
  ret = dict()
570
  for game_name in solved_games.keys():
571
  cur = []
572
- for level, level_id in zip(LEVELS[1:4], LEVEL_IDS[1:4]):
573
- if os.path.exists(_get_file_output(game_name, level_id, uid)):
 
 
 
 
 
 
574
  cur.append(level)
575
  ret[game_name] = cur
576
- return ret
577
 
578
 
579
  # %%
 
3
  import time
4
  import pandas as pd
5
  import gradio as gr
6
+ import hashlib
7
+ from io import BytesIO
 
 
 
8
 
9
  from textgames import GAME_NAMES, LEVEL_IDS, LEVELS, new_game, preload_game, game_filename
10
  from textgames.islands.islands import Islands
 
14
 
15
 
16
  # %%
17
+ import google.auth
18
+ from googleapiclient.discovery import build
19
+ from googleapiclient.errors import HttpError
20
+ from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload
21
+
22
+
23
+ # %%
24
+ def declare_components(demo, greet):
25
  with gr.Row():
26
  with gr.Column(scale=1):
27
  m = gr.Markdown("Welcome to TextGames!", elem_id="md-greeting")
 
33
  level_radio = gr.Radio(LEVELS, label="Level", elem_id="radio-level-name")
34
  new_game_btn = gr.Button("Start Game", elem_id="btn-start-game")
35
  render_toggle = gr.Checkbox(False, visible=False, interactive=False)
36
+
37
+ # cur_game_start = gr.BrowserState()
38
+ session_state = gr.State(0) # 0: menu selection, 1: game is ongoing, 2: game is solved.
39
+ is_solved = gr.State(0)
40
+ solved_games = gr.State({g: [] for _, g in game_radio.choices})
41
+ user_state = gr.State()
42
+ uid_state = gr.State()
43
+
44
+ session_state.change(
45
+ lambda s: session_state_change_fn(s, 2, 0, 2, 0),
46
+ [session_state], [game_radio, level_radio, new_game_btn, logout_btn], js=js_remove_input_helper,
47
+ )
48
+ new_game_btn.click(check_to_start_new_game, [game_radio, level_radio, user_state, uid_state], [session_state])
49
+ solved_games.change(solved_games_change_fn, solved_games, solved_games_df)
50
+ session_state.change(lambda s, r: (not r if s in [0, 1] else r), [session_state, render_toggle], [render_toggle])
51
+
52
+ demo.load(
53
+ greet, None, [m, user_state, uid_state], js=js_solved_games_df_and_remove_footers
54
+ ).then(
55
+ lambda: gr.update(interactive=False), None, [new_game_btn],
56
+ ).then(
57
+ check_played_game, [solved_games, uid_state], [solved_games, solved_games_df]
58
+ ).then(
59
+ lambda: gr.update(interactive=True), None, [new_game_btn],
60
+ )
61
+
62
+ return (
63
+ (m, logout_btn, solved_games_df, game_radio, level_radio, new_game_btn, render_toggle),
64
+ (session_state, is_solved, solved_games, user_state, uid_state),
65
+ )
66
 
67
 
68
  # %%
69
+ _cksm_methods, _cksm_methods_str = (
70
+ [hashlib.md5, hashlib.sha1], "md5Checksum, sha1Checksum",
71
+ # [hashlib.md5, hashlib.sha1, hashlib.sha256], "md5Checksum, sha1Checksum, sha256Checksum",
72
+ )
73
+ _folder_id = "1qStKuVerAQPsXagngfzlNg8PdAR5hupA"
74
  _creds_dict = {
75
  "type": "service_account",
76
  "project_id": os.getenv("GOOGLE_AUTH_CREDS_PROJECT_ID", ""),
 
89
  _files = _service.files()
90
 
91
 
92
+ #%%
93
+ css = """
94
+ #lintao-helper-btn {background: darkgreen; color: white;}
95
+ .lintao-cell-highlight {background: var(--border-color-primary);}
96
+ //.lintao-border {border-style: solid; border-color: var(--body-text-color-subdued);}
97
+ """
98
+
99
+
100
  # %%
101
  js_remove_input_helper = """(s) => {
102
  var el = document.getElementById('lintao-container');
 
130
  container.style.display = 'grid';
131
  container.style.gridTemplateColumns = container.style.gridTemplateRows = `repeat(${{grid_N}}, ${{grid_px}}px)`;
132
  container.style.gap = '1px';
133
+ container.style.border = '2px solid';
134
  container.style.width = 'max-content';
135
  container.style.margin = '5px 0px 5px 40px';
136
+ container.style.padding = '1px';
137
  container.id = 'lintao-container';
138
 
139
  for (let i = 0; i < grid_N; ++i) {{
 
145
  cell.style.alignItems = 'center';
146
  cell.style.justifyContent = 'center';
147
  cell.style.fontSize = `${{grid_px/2}}px`;
148
+ cell.style.border = '1px solid var(--body-text-color-subdued)';
149
  cell.style.cursor = 'pointer';
150
  cell.id = `lintao-cell-${{i}}-${{j}}`;
151
 
 
177
 
178
  js_island_submit = """
179
  function island_submit(textarea, io_history) {{
180
+ const container = document.getElementById("lintao-container")
181
+ if (container === null) return [textarea, io_history];
182
  const grid_N = {N};
183
  var ret = "";
184
  for (let i = 0; i < grid_N; ++i) {{
 
198
  const N = {N};
199
  const grid_N = N*N,
200
  grid_px = 50,
201
+ border_px = 2;
202
  const mat = {mat};
203
 
204
  let is_numeric_sudoku = false;
 
214
  const container = document.createElement('div');
215
  container.style.display = 'grid';
216
  container.style.gridTemplateColumns = container.style.gridTemplateRows = `repeat(${{grid_N}}, ${{grid_px}}px)`;
217
+ container.style.border = `${{border_px}}px solid`;
 
218
  container.style.width = 'max-content';
219
  container.style.margin = '5px 0px 5px 40px';
220
+ container.style.padding = '0px';
221
  container.id = 'lintao-container';
222
 
223
  // Generate the grid
224
+ const highlightClass = 'lintao-cell-highlight';
225
  for (let i = 0; i < grid_N; ++i) {{
226
  for (let j = 0; j < grid_N; ++j) {{
227
+ const cell = document.createElement('div');
228
  cell.type = 'text';
229
  cell.maxLength = 1;
230
  cell.style.width = cell.style.height = `${{grid_px}}px`;
 
233
  cell.style.justifyContent = 'center';
234
  cell.style.textAlign = 'center';
235
  cell.style.fontSize = `${{grid_px/2}}px`;
236
+ cell.style.border = '1px solid var(--body-text-color-subdued)';
237
+ cell.style.margin = '0px';
238
+ //cell.style.outline = 'none';
239
+ //cell.style.color = 'var(--body-text-color)';
240
+ //cell.style.backgroundColor = 'black';
241
  cell.id = `lintao-cell-${{i}}-${{j}}`;
242
 
243
  if (mat[i][j] != '_') {{
244
+ cell.textContent = mat[i][j];
245
+ cell.style.color = 'var(--block-title-text-color)';
246
  cell.disabled = true;
247
+ }} else {{
248
+ cell.style.cursor = 'pointer';
249
+ cell.contentEditable = "true";
250
  }}
251
 
 
 
252
 
253
+ if (j % N === 0) cell.style.borderLeft = `${{border_px}}px solid var(--body-text-color)`;
254
+ if (j % N === (N-1)) cell.style.borderRight = `${{border_px}}px solid var(--body-text-color)`;
255
+ if (i % N === 0) cell.style.borderTop = `${{border_px}}px solid var(--body-text-color)`;
256
+ if (i % N === (N-1)) cell.style.borderBottom = `${{border_px}}px solid var(--body-text-color)`;
257
 
258
  // Allow only numbers 1-9 or A-I
259
  cell.addEventListener('input', (e) => {{
260
+ if ((N === 2 && (!(is_numeric_sudoku?/^[1-4]$/:/^[A-Da-d]$/).test(e.target.textContent))) ||
261
+ (N === 3 && (!(is_numeric_sudoku?/^[1-9]$/:/^[A-Ia-i]$/).test(e.target.textContent)))) {{
262
+ e.target.textContent = '';
263
  }}
264
+ e.target.textContent = e.target.textContent.toUpperCase();
265
  }});
266
 
267
  container.appendChild(cell);
 
281
  const currentCol = i % grid_N;
282
 
283
  if (currentRow === row || currentCol === col || (Math.floor(currentRow / N) === Math.floor(row / N) && Math.floor(currentCol / N) === Math.floor(col / N))) {{
284
+ cell.classList.add(highlightClass);
285
  }} else {{
286
+ cell.classList.remove(highlightClass);
287
  }}
288
  }}
289
  }});
290
 
291
  container.addEventListener('focusout', () => {{
292
  for (let i = 0; i < grid_N * grid_N; i++) {{
293
+ container.children[i].classList.remove(highlightClass);
294
  }}
295
  }});
296
 
 
301
 
302
  js_sudoku_submit = """
303
  function sudoku_submit(textarea, io_history) {{
304
+ const container = document.getElementById("lintao-container")
305
+ if (container === null) return [textarea, io_history];
306
  const N = {N};
307
  const grid_N = N*N;
308
  var ret = "";
309
  for (let i = 0; i < grid_N; ++i) {{
310
  if (i > 0) ret += '\\n';
311
  for (let j = 0; j < grid_N; ++j) {{
312
+ ret += document.getElementById(`lintao-cell-${{i}}-${{j}}`).textContent;
313
  }}
314
  }}
315
  return [ret, io_history];
 
327
  container.style.display = 'grid';
328
  container.style.gridTemplateColumns = container.style.gridTemplateRows = `repeat(${{grid_N}}, ${{grid_px}}px)`;
329
  container.style.gap = '1px';
330
+ container.style.border = '2px solid';
331
  container.style.width = 'max-content';
332
  container.style.margin = '5px 0px 5px 40px';
333
+ container.style.padding = '1px';
334
  container.id = 'lintao-container';
335
 
336
  // Generate the grid
 
346
  cell.style.justifyContent = 'center';
347
  cell.style.textAlign = 'center';
348
  cell.style.fontSize = `${{grid_px/2}}px`;
349
+ cell.style.border = '1px solid var(--body-text-color-subdued)';
350
+ cell.style.backgroundColor = 'var(--body-background-fill)';
351
  cell.style.cursor = 'pointer';
352
  cell.id = `lintao-cell-${{i}}-${{j}}`;
353
 
 
369
 
370
  js_crossword_submit = """
371
  function crossword_submit(textarea, io_history) {{
372
+ const container = document.getElementById("lintao-container")
373
+ if (container === null) return [textarea, io_history];
374
  const grid_N = {N};
375
  var ret = "";
376
  for (let i = 0; i < grid_N; ++i) {{
 
391
  listContainer.style.listStyle = 'none';
392
  listContainer.style.padding = '0';
393
  listContainer.style.width = '20em';
394
+ listContainer.style.border = '2px solid';
395
  listContainer.style.margin = '5px 0px 5px 40px';
396
  listContainer.id = 'lintao-container';
397
 
 
404
  listItem.textContent = itemText;
405
  listItem.draggable = true;
406
  listItem.style.padding = '10px';
407
+ listItem.style.border = '1px solid';
408
  listItem.style.margin = '3px';
409
+ //listItem.style.backgroundColor = 'var(--body-background-fill)';
410
  listItem.style.cursor = 'grab';
411
  listItem.id = `lintao-item-${{index}}`;
412
 
 
414
  listItem.addEventListener('dragstart', (e) => {{
415
  const draggedIndex = Array.from(listContainer.children).indexOf(listItem);
416
  e.dataTransfer.setData('text/plain', draggedIndex);
417
+ listItem.style.backgroundColor = 'var(--block-background-fill)';
418
  }});
419
 
420
  listItem.addEventListener('dragover', (e) => {{
421
  e.preventDefault();
422
+ listItem.style.backgroundColor = 'var(--border-color-primary)';
423
  }});
424
 
425
  listItem.addEventListener('dragleave', () => {{
426
+ listItem.style.backgroundColor = 'var(--body-background-fill)';
427
  }});
428
 
429
  listItem.addEventListener('drop', (e) => {{
 
437
  listContainer.insertBefore(draggedItem, targetIndex > draggedIndex ? listItem.nextSibling : listItem);
438
  }}
439
 
440
+ listItem.style.backgroundColor = 'var(--body-background-fill)';
441
  }});
442
 
443
  listItem.addEventListener('dragend', () => {{
444
+ listItem.style.backgroundColor = 'var(--body-background-fill)';
445
  }});
446
 
447
  listContainer.appendChild(listItem);
 
454
 
455
  js_ordering_submit = """
456
  function ordering_submit(textarea, io_history) {{
457
+ const container = document.getElementById("lintao-container")
458
+ if (container === null) return [textarea, io_history];
459
  var ret = "";
460
+ container.childNodes.forEach(
 
461
  (c, i) => {{
462
  if (i>0) ret += '\\n';
463
  ret += c.textContent;
 
483
  return f"{fd}/{fn_prefix}_-_{game_filename(game_name)}_{level_id}.pkl"
484
 
485
 
486
+ # %%
487
+ def _is_checksum_same(fp_out, matches=None, mime_type="application/octet-stream"):
488
+ if matches is None:
489
+ matches = _files.list(
490
+ q=f"'{_folder_id}' in parents and mimeType='{mime_type}' and name = '{fp_out.rsplit('/', 1)[-1]}'",
491
+ fields=f"files(name, id, {_cksm_methods_str})",
492
+ ).execute()['files']
493
+ if not os.path.exists(fp_out):
494
+ return None, None, matches
495
+ with open(fp_out, "rb") as o:
496
+ _local = BytesIO(o.read()).getvalue()
497
+ _local_hash = [m(_local).hexdigest() for m in _cksm_methods]
498
+ for i, match in enumerate(matches):
499
+ if all(a == b for a, b in zip(_local_hash, [match[k] for k in _cksm_methods_str.split(", ")])):
500
+ return True, i, matches
501
+ return False, -1, matches
502
+
503
+
504
+ # %%
505
+ def upload_to_drive(fp_out, matches=None, mime_type="application/octet-stream", compare_checksum=True):
506
+ if compare_checksum:
507
+ same_checksum, _, _ = _is_checksum_same(fp_out, matches, mime_type)
508
+ # same_checksum, _, _ = _is_checksum_same(
509
+ # fp_out, **{k: v for k, v in [('matches', matches), ('mime_type', mime_type)] if v})
510
+ if same_checksum:
511
+ return
512
+ fn = fp_out.rsplit("/", 1)[-1]
513
+ file_metadata = {"name": fn, "parents": [_folder_id]}
514
+ media = MediaFileUpload(fp_out)
515
+ try:
516
+ _files.create(body=file_metadata, media_body=media).execute()
517
+ except HttpError as error:
518
+ msg = f"Failed to upload the file, error: {error}"
519
+ print(msg)
520
+ gr.Error(msg)
521
+
522
+
523
+ # %%
524
+ def download_from_drive(fp_out, matches=None, mime_type="application/octet-stream", compare_checksum=True):
525
+ if compare_checksum and os.path.exists(fp_out):
526
+ same_checksum, i, matches = _is_checksum_same(fp_out, matches, mime_type)
527
+ if same_checksum:
528
+ return
529
+ if matches is None:
530
+ _, _, matches = _is_checksum_same(fp_out, matches, mime_type)
531
+ if len(matches) == 0:
532
+ return
533
+ else:
534
+ if len(matches) > 1:
535
+ gr.Warning(f"Multiple matches found! {fp_out.rsplit('/', 1)[-1].split('_-_', 1)[-1]}")
536
+ b_io, request = BytesIO(), _files.get_media(fileId=matches[0]['id'])
537
+ downloader = MediaIoBaseDownload(b_io, request)
538
+ if os.path.exists(fp_out):
539
+ print(f"Deleting and re-download... ({fp_out})")
540
+ os.remove(fp_out)
541
+ done = False
542
+ while not done:
543
+ status, done = downloader.next_chunk()
544
+ with open(fp_out, "ab") as o:
545
+ o.write(b_io.getvalue())
546
+
547
+
548
  # %%
549
  def start_new_game(game_name, level, session_state_component, is_solved_component, solved_games_component,
550
  user=None, show_timer=False, uid=None):
 
562
  preload_game(game_name, difficulty_level, user)
563
  )
564
  cur_game.attach_stats_output_(fp_out)
565
+ cur_game.flush_stats_(user_info_to_flush=user)
566
 
567
  def add_msg(new_msg, prev_msg):
568
+ if len(new_msg) > 200:
569
+ new_msg = new_msg[:200]
570
+ gr.Warning("your input is too long! It has been truncated.")
571
  user_input = '\n'.join(new_msg.split())
572
  solved, val_msg = cur_game.validate(user_input)
573
  response = ("Correct guess" if solved else "Bad guess (Wrong Answer)") + "\n" + val_msg
 
579
 
580
  gr.Markdown(
581
  """
582
+ > ### ‼️ Do ***<span style="color:red">NOT</span>*** refresh this page. ‼️<br>
583
  > #### ⚠️ Refreshing the page equals "Give-up 😭" ⚠️
 
 
584
  """
585
  )
586
  showhide_helper_btn = gr.Button("Show Input Helper (disabling manual input)", elem_id="lintao-helper-btn")
 
622
 
623
  def _forfeiting(confirmed, _solved_games):
624
  if confirmed:
625
+ gr.Info("Sad to see you go... Wrapping things up...")
626
  cur_game.finish_stats_(forfeit=True)
627
+ if level in LEVELS and level not in _solved_games[game_name]:
628
  _solved_games[game_name].append(level)
629
+ upload_to_drive(fp_out)
630
  return 0, _solved_games
631
  return 1, _solved_games
632
+ give_up_checkbox.change(
633
+ lambda: (gr.update(interactive=False), gr.update(interactive=False)), None, [submit_btn, give_up_btn]
634
+ ).then(
635
+ _forfeiting, [give_up_checkbox, solved_games_component], [session_state_component, solved_games_component]
636
+ ).then(
637
+ lambda: (gr.update(interactive=True), gr.update(interactive=True)), None, [submit_btn, give_up_btn]
638
+ )
639
 
640
+ def game_is_solved(_is_solved, _session_state, _solved_games, progress=gr.Progress()):
641
  if _is_solved:
642
+ if level in LEVELS and level not in _solved_games[game_name]:
643
  _solved_games[game_name].append(level)
644
  return (
645
  2,
646
  gr.update(visible=False, interactive=False),
647
  gr.update(visible=False, interactive=False),
 
648
  _solved_games,
649
+ gr.update(visible=True, interactive=False),
650
  )
651
  else:
652
  return (
653
+ _session_state, gr.update(), gr.update(), _solved_games, gr.update()
654
  )
655
 
656
+ def finalize_game(_is_solved):
657
+ if _is_solved:
658
+ gr.Info("Reporting... Please click the button when available...")
659
+ upload_to_drive(fp_out)
660
+ return gr.update(interactive=True)
661
+ return gr.update()
 
 
 
662
 
663
  is_solved_component.change(
664
  game_is_solved,
665
  [is_solved_component, session_state_component, solved_games_component],
666
+ [session_state_component, submit_btn, give_up_btn, solved_games_component, finish_btn],
667
+ ).then(
668
+ finalize_game, [is_solved_component], [finish_btn],
669
  )
670
  finish_btn.click(
 
 
671
  lambda: (0, 0), None, [session_state_component, is_solved_component]
672
  )
673
 
 
682
  raise gr.Error(f"You have done this game already.<br/>{game_name} - {level}")
683
  if user is None:
684
  gr.Warning("no user, game will be generated randomly")
685
+ # else:
686
+ # if not user['email_verified']:
687
+ # gr.Warning("please verify your email address")
688
+ # elif user['email_verified'] == "mockuser":
689
+ # gr.Info("game will load with a mocked-up user")
690
  return 1
691
 
692
 
693
  # %%
694
+ def check_played_game(solved_games, uid, progress=gr.Progress()):
695
+ matches = _files.list(
696
+ q=f"'{_folder_id}' in parents and mimeType='application/octet-stream' and name contains '{uid}_-_'",
697
+ fields=f"files(name, id, {_cksm_methods_str})",
698
+ ).execute()['files']
699
  ret = dict()
700
  for game_name in solved_games.keys():
701
  cur = []
702
+ for level, level_id in zip(LEVELS, LEVEL_IDS):
703
+ fp_out = _get_file_output(game_name, level_id, uid)
704
+ _matches = list(filter(lambda m: fp_out.endswith(m['name']), matches))
705
+ if os.path.exists(fp_out):
706
+ upload_to_drive(fp_out, _matches)
707
+ else:
708
+ download_from_drive(fp_out, _matches)
709
+ if os.path.exists(fp_out):
710
  cur.append(level)
711
  ret[game_name] = cur
712
+ return ret, gr.update()
713
 
714
 
715
  # %%
play_with_auth.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ # os.environ.setdefault("GRADIO_SERVER_PORT", "1080")
4
+ # os.environ.setdefault("TEXTGAMES_SHOW_HIDDEN_LEVEL", "1")
5
+ os.environ.setdefault("TEXTGAMES_LOADGAME_DIR", "problemsets")
6
+ os.environ.setdefault("TEXTGAMES_LOADGAME_ID", "42")
7
+ os.environ.setdefault("TEXTGAMES_MOCKUSER", "")
8
+ os.environ.setdefault("TEXTGAMES_OUTPUT_DIR", "user_outputs")
9
+ os.environ.setdefault("TEXTGAMES_HASH_USER", "")
10
+ favicon_path = "textgames-scrabble-black2-ss.png"
11
+
12
+ #%%
13
+ from play_helper import css, declare_components, start_new_game
14
+ from typing import Optional
15
+ import gradio as gr
16
+ import hashlib
17
+
18
+
19
+ #%%
20
+ import uvicorn
21
+ from fastapi import FastAPI, Depends, Request
22
+ from starlette.config import Config
23
+ from starlette.responses import RedirectResponse, FileResponse
24
+ from starlette.middleware.sessions import SessionMiddleware
25
+ from authlib.integrations.starlette_client import OAuth, OAuthError
26
+
27
+ app = FastAPI()
28
+
29
+ # Replace these with your own OAuth settings
30
+ GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID")
31
+ GOOGLE_CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET")
32
+ SECRET_KEY = os.environ.get("SECRET_KEY", "a_very_secret_key")
33
+
34
+ # Set up OAuth
35
+ config_data = {'GOOGLE_CLIENT_ID': GOOGLE_CLIENT_ID, 'GOOGLE_CLIENT_SECRET': GOOGLE_CLIENT_SECRET}
36
+ starlette_config = Config(environ=config_data)
37
+ oauth = OAuth(starlette_config)
38
+ oauth.register(
39
+ name='google',
40
+ server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
41
+ client_kwargs={'scope': 'openid email profile'},
42
+ )
43
+
44
+ app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
45
+
46
+ _HASHER = (hashlib.blake2b, {"digest_size": 16, "key": SECRET_KEY.encode('utf-8')})
47
+
48
+
49
+ def _hash_msg(msg):
50
+ if isinstance(msg, str):
51
+ msg = msg.encode('utf-8')
52
+ m = _HASHER[0](**_HASHER[1])
53
+ m.update(msg)
54
+ return m.hexdigest()
55
+
56
+
57
+ # Dependency to get the current user
58
+ def get_user(request: Request) -> Optional[dict]:
59
+ if user := request.session.get('user'):
60
+ return user
61
+ elif username := os.getenv("TEXTGAMES_MOCKUSER", ""):
62
+ return {'name': username, 'email': username, 'email_verified': False}
63
+ else:
64
+ return
65
+
66
+
67
+ def get_username(request: Request):
68
+ user = get_user(request)
69
+ if user:
70
+ return user['email']
71
+ return None
72
+
73
+
74
+ @app.get('/favicon.ico', include_in_schema=False)
75
+ async def favicon():
76
+ return FileResponse(favicon_path)
77
+
78
+
79
+ @app.get('/')
80
+ def public(user: str = Depends(get_username)):
81
+ if user:
82
+ return RedirectResponse(url='/TextGames')
83
+ else:
84
+ return RedirectResponse(url='/login')
85
+
86
+
87
+ @app.route('/logout')
88
+ async def logout(request: Request):
89
+ request.session.pop('user', None)
90
+ if os.getenv('TEXTGAMES_MOCKUSER', ''):
91
+ os.environ['TEXTGAMES_MOCKUSER'] = ''
92
+ return RedirectResponse(url='/')
93
+
94
+
95
+ @app.route('/do-login')
96
+ async def login(request: Request):
97
+ redirect_uri = request.url_for('auth')
98
+ # If your app is running on https, you should ensure that the
99
+ # `redirect_uri` is https, e.g. uncomment the following lines:
100
+
101
+ from urllib.parse import urlparse, urlunparse
102
+ redirect_uri = urlunparse(urlparse(str(redirect_uri))._replace(scheme='https'))
103
+ return await oauth.google.authorize_redirect(request, redirect_uri)
104
+
105
+
106
+ @app.route('/auth')
107
+ async def auth(request: Request):
108
+ try:
109
+ access_token = await oauth.google.authorize_access_token(request)
110
+ except OAuthError:
111
+ return RedirectResponse(url='/')
112
+ request.session['user'] = dict(access_token)["userinfo"]
113
+ return RedirectResponse(url='/')
114
+
115
+
116
+ def greet(request: gr.Request):
117
+ user = get_user(request.request)
118
+ uid = _hash_msg(user['email']) if os.getenv("TEXTGAMES_HASH_USER", "") else user['email']
119
+ return f"""
120
+ Welcome to TextGames, {user['name']}!<br />
121
+ <{user['email'].replace('@', '{at}')}> ({'' if user['email_verified'] else 'NON-'}verified email)
122
+ """, user, uid
123
+
124
+
125
+ with gr.Blocks(title="TextGames") as login_demo:
126
+ gr.Markdown("Welcome to TextGames!")
127
+ # gr.Button("Login", link="/do-login")
128
+ gr.Button("🚪\tLogin", link="/do-login", icon=None)
129
+
130
+ app = gr.mount_gradio_app(app, login_demo, path="/login")
131
+
132
+ with gr.Blocks(title="TextGames", css=css, delete_cache=(3600, 3600)) as demo:
133
+ ((m, logout_btn, solved_games_df, game_radio, level_radio, new_game_btn, render_toggle),
134
+ (session_state, is_solved, solved_games, user_state, uid_state),
135
+ ) = declare_components(demo, greet)
136
+
137
+ @gr.render(inputs=[game_radio, level_radio, user_state, session_state, uid_state], triggers=[render_toggle.change])
138
+ def _start_new_game(game_name, level, user, _session_state, _uid_state):
139
+ if _session_state in [1, 2]:
140
+ start_new_game(game_name, level, session_state, is_solved, solved_games, user=user, uid=_uid_state)
141
+
142
+
143
+ app = gr.mount_gradio_app(app, demo, path="/TextGames", auth_dependency=get_username)
144
+
145
+ if __name__ == '__main__':
146
+ uvicorn.run(app,
147
+ port=int(os.getenv("GRADIO_SERVER_PORT", "7860")),
148
+ host=os.getenv("UVICORN_SERVER_HOST", "127.0.0.1"),
149
+ ssl_keyfile=os.getenv("SSL_KEYFILE", None),
150
+ ssl_certfile=os.getenv("SSL_CERTFILE", None),
151
+ )
152
+
153
+
154
+ #%%
155
+
156
+
157
+ #%%
158
+
read_pkl.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+
3
+ def read_pkl(fp):
4
+ with open(fp, "rb") as f:
5
+ data = []
6
+ try:
7
+ while True:
8
+ data.append(pickle.load(f))
9
+ except EOFError as e:
10
+ pass
11
+ return data
textgames/__init__.py CHANGED
@@ -34,9 +34,9 @@ SINGLE_LINE_GAME_IDS = list(map(lambda g: GAME_IDS[GAME_NAMES.index(g.get_game_n
34
  [PasswordGame, BracketGame, StringSearch, AnagramScribble]
35
  ))
36
 
37
- LEVEL_IDS = ["0", "1", "2", "3", "4", "00"]
38
- LEVELS = ["🔰\tSample #1", "🚅\tEasy", "🚀\tMedium", "🛸\tHard"]
39
- LEVELS_HIDDEN = ["🌌\tInsane", "🔰\tSample #2"]
40
  _show_hidden_level_ = os.getenv("TEXTGAMES_SHOW_HIDDEN_LEVEL", False)
41
  if _show_hidden_level_:
42
  LEVELS, LEVELS_HIDDEN = LEVELS + LEVELS_HIDDEN, []
@@ -62,14 +62,16 @@ def _game_class_from_name(game_name):
62
 
63
  def preload_game(game_name, level_id, user):
64
  game_cls = _game_class_from_name(game_name)
65
- email_sid_dict = read_csv("session_email.csv").dropna().set_index("EMAIL").SID.to_dict()
 
 
66
  sid = email_sid_dict.get(user["email"])
67
- print(f"preload_game('{game_name}', '{level_id}', '{user['email']}')", game_cls, sid, sep="\n\t")
68
 
69
  with open(f"problemsets/{game_filename(game_name)}_{level_id}.json", "r", encoding="utf8") as f:
70
  sid_prompt_dict = json.load(f)
71
  prompt = sid_prompt_dict.get(sid)
72
- print("Loaded prompt:", prompt, sep="\n")
73
 
74
  return _reload(prompt, game_cls)
75
 
 
34
  [PasswordGame, BracketGame, StringSearch, AnagramScribble]
35
  ))
36
 
37
+ LEVEL_IDS = ["1", "2", "3", "4", "0", "00"]
38
+ LEVELS = ["🚅\tEasy", "🚀\tMedium", "🛸\tHard"]
39
+ LEVELS_HIDDEN = ["🌌\tInsane", "🔰\tSample #1", "🔰\tSample #2"]
40
  _show_hidden_level_ = os.getenv("TEXTGAMES_SHOW_HIDDEN_LEVEL", False)
41
  if _show_hidden_level_:
42
  LEVELS, LEVELS_HIDDEN = LEVELS + LEVELS_HIDDEN, []
 
62
 
63
  def preload_game(game_name, level_id, user):
64
  game_cls = _game_class_from_name(game_name)
65
+ email_sid_dict = read_csv(
66
+ f"{os.getenv('TEXTGAMES_OUTPUT_DIR')}/textgames_userauth.tsv", sep='\t'
67
+ ).dropna().set_index("EMAIL").SID.to_dict()
68
  sid = email_sid_dict.get(user["email"])
69
+ print(f"preload_game('{game_name}', '{level_id}', '{user['email']}') on {sid}")
70
 
71
  with open(f"problemsets/{game_filename(game_name)}_{level_id}.json", "r", encoding="utf8") as f:
72
  sid_prompt_dict = json.load(f)
73
  prompt = sid_prompt_dict.get(sid)
74
+ # print("Loaded prompt:", prompt, sep="\n")
75
 
76
  return _reload(prompt, game_cls)
77
 
textgames/base_game.py CHANGED
@@ -42,11 +42,11 @@ class BaseGame:
42
  assert not self.stats_filepath
43
  self.stats_filepath = filepath
44
 
45
- def flush_stats_(self, user=None):
46
  if self.stats_filepath:
47
  with open(self.stats_filepath, mode='ab') as o:
48
- if user:
49
- pickle.dump((time.time(), user), o)
50
  else:
51
  pickle.dump((
52
  time.time(),
@@ -112,7 +112,4 @@ def _is_game_reloadable(original_game: BaseGame) -> bool:
112
  original_game_states = {k: v for k, v in vars(original_game).items() if k not in exclude_states}
113
  loaded_game_states = {k: v for k, v in vars(loaded_game).items() if k not in exclude_states}
114
 
115
- ret = (original_game_states == loaded_game_states) and (original_game.get_prompt() == loaded_game.get_prompt())
116
- if not ret:
117
- pass
118
- return ret
 
42
  assert not self.stats_filepath
43
  self.stats_filepath = filepath
44
 
45
+ def flush_stats_(self, user_info_to_flush=None):
46
  if self.stats_filepath:
47
  with open(self.stats_filepath, mode='ab') as o:
48
+ if user_info_to_flush:
49
+ pickle.dump((time.time(), user_info_to_flush), o)
50
  else:
51
  pickle.dump((
52
  time.time(),
 
112
  original_game_states = {k: v for k, v in vars(original_game).items() if k not in exclude_states}
113
  loaded_game_states = {k: v for k, v in vars(loaded_game).items() if k not in exclude_states}
114
 
115
+ return (original_game_states == loaded_game_states) and (original_game.get_prompt() == loaded_game.get_prompt())
 
 
 
textgames/ordering_text/ordering_text.py CHANGED
@@ -170,7 +170,7 @@ class ConsecutiveScoring(Scoring):
170
  pattern += cur_pattern
171
  pattern += f"{{{n}}}" if (n > 1) else ""
172
  n = 1
173
- self._pattern = re.compile(pattern)
174
 
175
  self.prompt = None
176
 
 
170
  pattern += cur_pattern
171
  pattern += f"{{{n}}}" if (n > 1) else ""
172
  n = 1
173
+ self._pattern = re.compile(f"(?=({pattern}))")
174
 
175
  self.prompt = None
176
 
textgames_userauth_generate.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ from dotenv import load_dotenv
4
+
5
+ # %%
6
+ load_dotenv("oauth_environ_google.env")
7
+
8
+ # %%
9
+ os.system("python ~/pwd_generator.py --spchar \"+()!#$%&@?.;/\" -n 1000 -r 3 -l 24 > textgames_passwords.txt")
10
+
11
+ # %%
12
+ with open("textgames_passwords.txt", "r", encoding="utf8") as i:
13
+ passwords = [("", "", l.strip(), f"session_{k:04}") for k, l in enumerate(i)]
14
+
15
+ # %%
16
+ df = pd.DataFrame(passwords, columns=["EMAIL", "NAME", "PASSWORD", "SID"])
17
+
18
+ # %%
19
+ df.to_csv("textgames_userauth.tsv", index=False, sep="\t")
20
+
21
+ # %%
22
+
23
+
24
+ # %%
25
+
26
+
27
+ # %%
28
+
29
+
30
+ # %%
31
+
32
+
33
+ # %%
34
+
35
+
36
+ # %%
37
+
38
+
39
+ # %%
40
+
41
+
42
+ # %%
43
+
44
+