From d2dd9590dd710d283e85581b3fbb6277652ff969 Mon Sep 17 00:00:00 2001 From: Taylor Preston Date: Thu, 6 Jul 2023 15:01:56 -0400 Subject: [PATCH 1/5] feat: flashcard student view --- games/__init__.py | 1 + games/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 198 bytes games/__pycache__/games.cpython-38.pyc | Bin 0 -> 6185 bytes games/games.py | 323 +++++++++++++++++++++ games/static/README.txt | 19 ++ games/static/css/games.css | 302 +++++++++++++++++++ games/static/html/games.html | 5 + games/static/img/close_button.png | Bin 0 -> 355 bytes games/static/img/navigate_left.png | Bin 0 -> 309 bytes games/static/img/navigate_right.png | Bin 0 -> 285 bytes games/static/js/src/games.js | 259 +++++++++++++++++ games/translations/README.txt | 4 + games_xblock.egg-info/PKG-INFO | 10 + games_xblock.egg-info/SOURCES.txt | 13 + games_xblock.egg-info/dependency_links.txt | 1 + games_xblock.egg-info/entry_points.txt | 3 + games_xblock.egg-info/requires.txt | 1 + games_xblock.egg-info/top_level.txt | 1 + setup.py | 42 +++ 19 files changed, 984 insertions(+) create mode 100644 games/__init__.py create mode 100644 games/__pycache__/__init__.cpython-38.pyc create mode 100644 games/__pycache__/games.cpython-38.pyc create mode 100644 games/games.py create mode 100644 games/static/README.txt create mode 100644 games/static/css/games.css create mode 100644 games/static/html/games.html create mode 100644 games/static/img/close_button.png create mode 100644 games/static/img/navigate_left.png create mode 100644 games/static/img/navigate_right.png create mode 100644 games/static/js/src/games.js create mode 100644 games/translations/README.txt create mode 100644 games_xblock.egg-info/PKG-INFO create mode 100644 games_xblock.egg-info/SOURCES.txt create mode 100644 games_xblock.egg-info/dependency_links.txt create mode 100644 games_xblock.egg-info/entry_points.txt create mode 100644 games_xblock.egg-info/requires.txt create mode 100644 games_xblock.egg-info/top_level.txt create mode 100644 setup.py diff --git a/games/__init__.py b/games/__init__.py new file mode 100644 index 0000000..4ceda73 --- /dev/null +++ b/games/__init__.py @@ -0,0 +1 @@ +from .games import GamesXBlock diff --git a/games/__pycache__/__init__.cpython-38.pyc b/games/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87942a5bb009c86258d24b6357bf32b4f4ab1c32 GIT binary patch literal 198 zcmWIL<>g`kg6n=$Qsja3V-N=!FabFZKwK;UBvKes7;_kM8KW2(8B&;n88n$+G6ID) z8Eyr=CujR1KIr< Gh#3H(!!har literal 0 HcmV?d00001 diff --git a/games/__pycache__/games.cpython-38.pyc b/games/__pycache__/games.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f66d47d636f4d36c168bde06537f877df4480e33 GIT binary patch literal 6185 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 A$p8QV literal 0 HcmV?d00001 diff --git a/games/games.py b/games/games.py new file mode 100644 index 0000000..ea83095 --- /dev/null +++ b/games/games.py @@ -0,0 +1,323 @@ +"""An XBlock providing gamification capabilities.""" + +import pkg_resources +from web_fragments.fragment import Fragment +from xblock.core import XBlock + +#May need to import more or less field types later (https://github.com/openedx/XBlock/blob/master/xblock/fields.py) +from xblock.fields import Integer, Scope, String, Boolean, List#, Dict + +class GamesXBlock(XBlock): + """ + An XBlock for creating games. + + The Student view will display the game content and allow the student to interact + accordingly. + + The editor view will allow course authors to create and manipulate the games. + """ + + #Universal fields-------------------------------------------------------------- + title = String( + default="Game Title", + scope=Scope.content, + help="The title of the block to be displayed in the xblock." + ) + + #Change default to 'matching' for matching game and 'flashcards' for flashcards game + type = String( + default="flashcards", + scope=Scope.settings, + help="The kind of game this xblock is responsible for ('flashcards' or 'matching' for now)." + ) + + #Matching and flashcards will use the same list, but term_image and definition_image will only be used in flashcards for now + #DUMMY DATA + list = List( + default=[ + { + 'term_image': 'https://studio.stage.edx.org/static/studio/edx.org-next/images/studio-logo.005b2ebe0c8b.png', + 'definition_image': 'https://logos.openedx.org/open-edx-logo-tag.png', + 'term': 'Term 1', + 'definition': 'The definition of term 1 (moderate character length).' + }, + { + 'term_image': None, + 'definition_image': None, + 'term': 'T2', + 'definition': 'Def of T2 - short.' + }, + { + 'term_image': 'https://logos.openedx.org/open-edx-logo-tag.png', + 'definition_image': 'https://studio.stage.edx.org/static/studio/edx.org-next/images/studio-logo.005b2ebe0c8b.png', + 'term': 'The Third Term', + 'definition': 'The definition of term 3. This one is far longer for testing purposes, so long in fact that it should certainly warrant a new line.' + }, + { + 'term_image': None, + 'definition_image': None, + 'term': 'T4', + 'definition': 'D4' + }, + { + 'term_image': None, + 'definition_image': None, + 'term': 'T5', + 'definition': 'D5' + }, + { + 'term_image': None, + 'definition_image': None, + 'term': 'T6', + 'definition': 'D6' + }, + { + 'term_image': None, + 'definition_image': None, + 'term': 'T7', + 'definition': 'D7' + }, + { + 'term_image': None, + 'definition_image': None, + 'term': 'T8', + 'definition': 'D8' + }, + { + 'term_image': None, + 'definition_image': None, + 'term': 'T9', + 'definition': 'D9' + }, + { + 'term_image': None, + 'definition_image': None, + 'term': 'T10', + 'definition': 'D10' + }, + { + 'term_image': None, + 'definition_image': None, + 'term': 'T11', + 'definition': 'D11' + }, + ], + scope=Scope.content, + help="The list of terms and definitions." + ) + + list_length = Integer( + default=len(list.default), + scope=Scope.content, + help="TEMP for HTML - WILL LIKELY NEED TO CHANGE WHEN DUMMY DATA IS NO LONGER USED" + ) + + #Flashcard game fields-------------------------------------------------------------- + list_index = Integer( + default=0, + scope=Scope.settings, + help="Determines which flashcard from the list is currently visible." + ) + + term_is_visible = Boolean( + default=True, + scope=Scope.settings, + help="True when the term is visible and false when the definition is visible in the flashcards game." + ) + + #Matching game fields-------------------------------------------------------------- + best_time = Integer( + default=None, + scope=Scope.user_info, + help="The user's best time for the matching game." + ) + #need field for selected containers - probably a dictionary of two indices referring to the list field's values + #if the dictionary of indices doesn't work, two separate fields is also viable + #may need field for page number if list_length field is not enough + #field for current time likely necessary, will need ot look up how to use it in real-time + + #Following fields for editor only-------------------------------------------------------------- + shuffle = Boolean( + default=True, + scope=Scope.settings, + help="Whether to shuffle or not. For flashcards only?" + ) + timer = Boolean( + default=True, + scope=Scope.settings, + help="Whether to enable the timer for the matching game." + ) + + #Important functions (unmodified)-------------------------------------------------------------- + def resource_string(self, path): + """Handy helper for getting resources from our kit.""" + data = pkg_resources.resource_string(__name__, path) + return data.decode("utf8") + + def student_view(self, context=None): + """ + The primary view of the GamesXBlock, shown to students + when viewing courses. + """ + html = self.resource_string("static/html/games.html") + frag = Fragment(html.format(self=self)) + frag.add_css(self.resource_string("static/css/games.css")) + frag.add_javascript(self.resource_string("static/js/src/games.js")) + frag.initialize_js('GamesXBlock') + return frag + + #Universal handlers-------------------------------------------------------------- + @XBlock.json_handler + def expand_game(self, data, suffix=''): + """ + A handler to expand the game from its title block. + """ + description = "ERR: self.type not defined or invalid" + if self.type == "flashcards": + description = "Click each card to reveal the definition" + elif self.type == "matching": + description = "Match each term with the correct definition" + return { + 'title': self.title, + 'description': description, + 'type': self.type + } + + @XBlock.json_handler + def start_game(self, data, suffix=''): + """ + A handler to begin the game. + """ + return { + 'term_image': self.list[self.list_index]['term_image'], + 'definition_image': self.list[self.list_index]['definition_image'], + 'term': self.list[self.list_index]['term'], + 'list_length': self.list_length, + } + + @XBlock.json_handler + def close_game(self, data, suffix=''): + """ + A handler to close the game to its title block. + """ + if self.type == "flashcards": + self.term_is_visible=True + self.list_index=0 + return { + 'title': self.title + } + + @XBlock.json_handler + def display_help(self, data, suffix=''): + """ + A handler to display a tooltip message above the help icon. + """ + message = "ERR: self.type not defined or invalid" + if self.type == "flashcards": + message = "Click each card to reveal the definition" + elif self.type == "matching": + message = "Match each term with the correct definition" + return {'message': message} + + + #Flashcards handlers-------------------------------------------------------------- + @XBlock.json_handler + def flip_flashcard(self, data, suffix=''): + """ + A handler to flip the flashcard from term to definition + and vice versa. + """ + + #flip term_is_visible first to show definition + self.term_is_visible = not(self.term_is_visible) + + #term_is_visible has already been flipped, so the conditional evaluates the original value by flipping it again + #When reviewing this logic, pretend that the not() function below is not there since term_is_visible was already flipped once above + if not(self.term_is_visible): + return {'image': self.list[self.list_index]['definition_image'], 'text': self.list[self.list_index]['definition']} + return {'image': self.list[self.list_index]['term_image'], 'text': self.list[self.list_index]['term']} + + @XBlock.json_handler + def page_turn(self, data, suffix=''): + """ + A handler to turn the page to a new flashcard (left or right) in the list. + """ + #Always display the term first for a new flashcard. + self.term_is_visible = True + + if data['nextIndex'] == 'left': + if self.list_index>0: + self.list_index-=1 + #if the current index is 0, circulate to the last flashcard + else: + self.list_index=len(self.list)-1 + return {'term_image': self.list[self.list_index]['term_image'], 'term': self.list[self.list_index]['term'], 'index': self.list_index+1, 'list_length': self.list_length} + + #data['nextIndex'] == 'right' here + if self.list_index + + + + + """), + ("gamesblock", + """ + """) + ] + + """ + @XBlock.json_handler + def flip_timer(self, data, suffix=''): + self.timer = not(self.timer) + return {'timer': self.timer} + + @XBlock.json_handler + def flip_shuffle(self, data, suffix=''): + self.shuffle = not(self.shuffle) + return {'shuffle': self.shuffle} + """ + + ''' + # The following is another way to approach the list field - currently not used but may be useful after dummy data is no longer used. + default=[ + Dict( + default={'term': 'term1', 'definition': 'definition1'}, + scope=Scope.content, + help="The first flashcard in the list." + ), + Dict( + default={'term': 'term2', 'definition': 'definition2'}, + scope=Scope.content, + help="The second flashcard in the list." + ), + Dict( + default={'term': 'term3', 'definition': 'definition3'}, + scope=Scope.content, + help="The third flashcard in the list." + ) + ], + ''' + #) \ No newline at end of file diff --git a/games/static/README.txt b/games/static/README.txt new file mode 100644 index 0000000..0472ef6 --- /dev/null +++ b/games/static/README.txt @@ -0,0 +1,19 @@ +This static directory is for files that should be included in your kit as plain +static files. + +You can ask the runtime for a URL that will retrieve these files with: + + url = self.runtime.local_resource_url(self, "static/js/lib.js") + +The default implementation is very strict though, and will not serve files from +the static directory. It will serve files from a directory named "public". +Create a directory alongside this one named "public", and put files there. +Then you can get a url with code like this: + + url = self.runtime.local_resource_url(self, "public/js/lib.js") + +The sample code includes a function you can use to read the content of files +in the static directory, like this: + + frag.add_javascript(self.resource_string("static/js/my_block.js")) + diff --git a/games/static/css/games.css b/games/static/css/games.css new file mode 100644 index 0000000..cc33866 --- /dev/null +++ b/games/static/css/games.css @@ -0,0 +1,302 @@ +/* CSS for GamesXBlock */ + +/*Universal styles--------------------------------------------------------------*/ +.title-initial { + display: flex; + width: 649px; + padding: 18px 281px; + justify-content: center; + align-items: center; + gap: 9px; + + border-radius: 4px; + border: 1px solid var(--light-500, #E1DDDB); + background: #FFF; + + cursor: pointer; +} + +.background-block { + display: flex; + height: 533px; + padding: 24px; + flex-direction: column; + align-items: flex-start; + gap: 24px; + align-self: stretch; + + border-radius: 8px; + background: var(--extras-white, #FFF); + box-shadow: 0px 8px 48px 0px rgba(0, 0, 0, 0.08), 0px 20px 40px 0px rgba(0, 0, 0, 0.08); +} + +.title-persistent { + display: flex; + align-items: center; + gap: 6px; + + color: var(--primary-500, #00262B); + + /* Heading/H3 */ + font-size: 22px; + font-family: Inter; + font-style: normal; + font-weight: 700; + line-height: 28px; +} + +.close-button { + display: flex; + width: 24px; + height: 24px; + justify-content: center; + align-items: center; +} + +.close-background { + display: flex; + width: 36px; + height: 36px; + padding: 6px; + justify-content: center; + align-items: center; + flex-shrink: 0; + + border-radius: 9999999px; +} + +.close-image { + width: 24px; + height: 24px; + flex-shrink: 0; + + cursor: pointer; +} + +.start-block { + display: flex; + padding: 24px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + flex: 1 0 0; + align-self: stretch; + + border-radius: 8px; +} + +.start-button-flashcards, .start-button-matching { + display: flex; + padding: 10px 16px; + justify-content: center; + align-items: center; + gap: 8px; + + background: var(--primary-500, #00262B); + + color: #FFF; + font-size: 18px; + font-family: Inter; + font-style: normal; + font-weight: 500; + line-height: 24px; + + cursor: pointer; +} + +/*Flashcard styles--------------------------------------------------------------*/ +.flashcard-top { + display: flex; + justify-content: space-between; + align-items: flex-start; + align-self: stretch; +} + +.flashcard-description { + color: var(--primary-500, #00262B); + text-align: center; + font-size: 20px; + font-family: Inter; + font-style: normal; + font-weight: 500; + line-height: 28px; +} + +.flashcard-block { + display: flex; + height: 373px; + padding: 24px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + flex-shrink: 0; + align-self: stretch; + + border-radius: 8px; + border: 2px solid var(--light-300, #F2F0EF); + + cursor: pointer; +} + +.image { + max-height: 200px; + flex-shrink: 0; + + /*background: url(), lightgray 0px -18.962px / 100% 118.363% no-repeat;*/ +} + +.card-text { + color: var(--primary-500, #00262B); + /*leading-trim: both; + text-edge: cap; + */ + font-size: 18px; + font-family: Inter; + font-style: normal; + font-weight: 400; + line-height: 28px; +} + +.flashcard-footer { + display: flex; + justify-content: space-between; + align-items: center; + align-self: stretch; +} + +.spacer { + width: 24px; + height: 24px; +} + +.flashcard-navigation { + display: flex; + align-items: center; + gap: 16px; +} + +.flashcard-left-button, .flashcard-right-button { + display: flex; + width: 36px; + height: 36px; + padding: 6px; + justify-content: center; + align-items: center; + + border-radius: 999999984306749400px; + background: #F2F0EF; + + cursor: pointer; +} + +.flashcard-left-image, .flashcard-right-image { + width: 24px; + height: 24px; + flex-shrink: 0; +} + +.flashcard-navigation-text { + color: var(--primary-500, #00262B); + text-align: center; + /*leading-trim: both; + text-edge: cap; + */ + font-size: 20px; + font-family: Inter; + font-style: normal; + font-weight: 500; + line-height: normal; +} + +/*Matching styles--------------------------------------------------------------*/ + + + +/*TOOLTIP styles(move to universal later)--------------------------------------------------------------*/ +/* +.tooltip { + width: fit-content; + height: fit-content; + position: relative; + bottom: 100%; + left: -50%; + box-sizing: border-box; + padding: 0px; +} + +for the triangle +.tooltip::after { + content: ""; + position: absolute; + top: 92%; + left: 50%; + margin-left: -8px; + border-width: 8px; + border-style: solid; + border-color: var(--core-elm, #00262B) transparent transparent transparent; +} +*/ + +.help { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; +} + +.help-outline { + position: relative; + width: 24px; + height: 24px; + cursor: pointer; +} + +.tooltip { + width: 600%; + position: absolute; + bottom: 140%; + right: -250%; +} + +.tooltip::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + margin-left: -8px; + border-width: 8px; + border-style: solid; + border-color: var(--core-elm, #00262B) transparent transparent transparent; +} + +.tooltip-text { + color: var(--extras-white, #FFF); + text-align: center; + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + + display: flex; + padding: 4px 8px; + flex-direction: column; + align-items: center; + gap: 10px; + + border-radius: 4px; + background: var(--core-elm, #00262B); + + +} + +/* +.tooltip-polygon { + width: 13.856px; + height: 9px; + + fill: var(--core-elm, #00262B); +} +*/ \ No newline at end of file diff --git a/games/static/html/games.html b/games/static/html/games.html new file mode 100644 index 0000000..080552c --- /dev/null +++ b/games/static/html/games.html @@ -0,0 +1,5 @@ +

+

+
{self.title}
+
+

\ No newline at end of file diff --git a/games/static/img/close_button.png b/games/static/img/close_button.png new file mode 100644 index 0000000000000000000000000000000000000000..d2c29c31917512d1913a4fb80d99bb85864595b3 GIT binary patch literal 355 zcmeAS@N?(olHy`uVBq!ia0vp^DnP8t!3HF?V!UmE6lZ})WHAE+w=f7ZGR&GI0Tg5` z4sv&5Sa(k5C6L3C?&#~tz_78O`%fY(kpIKe#WAFU@$J-&yoU?~T=^pulnt1c-Y)5J zILUTHR#x($G=uPjXkr$r7^wCH$6lXjezR27>)gD>Dhwef3T-U9W=vTlIw_kop-m+`wAB3AoXQ=K zB0gE1iB&e*`Td_8!-Rn8A**~6oR}Y;75}rdAVtY>rIDwTZqO7hVTbNt%r}2rn4uM% z!c@R(I&s#`OTSa*$t+uY;lI+C*aI@|T_yKdIOX2G^4v?pRpZ*N{S%s6e%&!%8I&>I xcZGZQ=_tLOp5fl=HD1LE;#Dt2LGESH=if9zXq$UjIxq|vJYD@<);T3K0RWJ2f{6eC literal 0 HcmV?d00001 diff --git a/games/static/img/navigate_left.png b/games/static/img/navigate_left.png new file mode 100644 index 0000000000000000000000000000000000000000..7b163d6087eb6e7125212131493a77d8fd54c3f0 GIT binary patch literal 309 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%1|*NXY)uAIoCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{wYrv$B+ufx04OI4jG8F{a>LTu&ew+K#zFA zYV{8sUF=04n7(ny9lUqIljY#D)vo#q67fbaE+}?}yqUmX$I<`)BX2)Lqr>h8PU#P$ z-aoM3HibudW0~gn2i)RO8(2abj~6!|yU3&`^5aL7Mdw8edB=-hjekEJ_T(yNcG@WW z#PiVPbz2Mfdwp3f)N}Tr!BK^z9ii0~3DYG4ODnn$1PL`xntUenu5Xa|*)wMXpXjaZ z4w|Jg>B%``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{w7Zs$B+ufx0n67njAzLKAwNYbah$kY&K7E z-QB+ReiwM(s7y6q>Fp9Q;jOBb!;c`2!cc$bBE$WydCxu?p5ckO_FppJZEcVI62ZhL zxhoeejsD8xs3h|&)Y)pC^^u Date: Thu, 6 Jul 2023 15:36:44 -0400 Subject: [PATCH 2/5] feat: install files for xblock sdk --- games/__pycache__/__init__.cpython-38.pyc | Bin 198 -> 189 bytes games/__pycache__/games.cpython-38.pyc | Bin 6185 -> 6174 bytes games_xblock.egg-info/PKG-INFO | 8 ++------ games_xblock.egg-info/SOURCES.txt | 5 +++++ games_xblock.egg-info/entry_points.txt | 1 - 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/games/__pycache__/__init__.cpython-38.pyc b/games/__pycache__/__init__.cpython-38.pyc index 87942a5bb009c86258d24b6357bf32b4f4ab1c32..944669fb7967232cdfbe8d5be7a3ddcca75271b2 100644 GIT binary patch delta 43 xcmX@cxR;STl$V!_0SNZ;ET71o%4j*UP=dd>C|N%}F*miiA}J?7IeX$lRRH<{4Lkq< delta 52 zcmdnXc#M%dl$V!_0SK=9O_|7@%IGw)P(q?2DJMTUTQ?=OEHx*;AU8FyL_a+-H??@; G3{?P*xDgfr diff --git a/games/__pycache__/games.cpython-38.pyc b/games/__pycache__/games.cpython-38.pyc index f66d47d636f4d36c168bde06537f877df4480e33..85a2a141e5d957c98769cc255b179d9255b00735 100644 GIT binary patch delta 81 zcmZ2!FwcNDl$V!_0SFq!mZz9+xtA?3=XmM&$v3^NGQEG8X iex81QL26!VN`-!LQL=tIP+@UJQciwy_GV5_1%3d^-x;O= delta 92 zcmbPdu+o4xl$V!_0SLsHm#0{6pAyo2n3!S+e;gGcT*5y?$tMYEiL%NkLI+ taY=rjettn}UTR8(ennDFesZ>MN@`hZPJTgdYF>$cI#6fv=6@Ut`~dRZ9+v Date: Thu, 6 Jul 2023 16:06:27 -0400 Subject: [PATCH 3/5] feat: img folder removed. images replaced by svg. --- games/__pycache__/games.cpython-38.pyc | Bin 6174 -> 6176 bytes games/static/css/games.css | 140 +++++++++---------------- games/static/img/close_button.png | Bin 355 -> 0 bytes games/static/img/navigate_left.png | Bin 309 -> 0 bytes games/static/img/navigate_right.png | Bin 285 -> 0 bytes games/static/js/src/games.js | 19 ++-- 6 files changed, 60 insertions(+), 99 deletions(-) delete mode 100644 games/static/img/close_button.png delete mode 100644 games/static/img/navigate_left.png delete mode 100644 games/static/img/navigate_right.png diff --git a/games/__pycache__/games.cpython-38.pyc b/games/__pycache__/games.cpython-38.pyc index 85a2a141e5d957c98769cc255b179d9255b00735..773f72d65e90c34ff3f185e3aabba97d0ffcd783 100644 GIT binary patch delta 37 rcmbPdu)u&fl$V!_0SLOpmZw;5pAyo2n3!S+e;gbEpUau}um^ delta 35 pcmZ2rFwcNDl$V!_0SFq!mZz9+kr$r7^wCH$6lXjezR27>)gD>Dhwef3T-U9W=vTlIw_kop-m+`wAB3AoXQ=K zB0gE1iB&e*`Td_8!-Rn8A**~6oR}Y;75}rdAVtY>rIDwTZqO7hVTbNt%r}2rn4uM% z!c@R(I&s#`OTSa*$t+uY;lI+C*aI@|T_yKdIOX2G^4v?pRpZ*N{S%s6e%&!%8I&>I xcZGZQ=_tLOp5fl=HD1LE;#Dt2LGESH=if9zXq$UjIxq|vJYD@<);T3K0RWJ2f{6eC diff --git a/games/static/img/navigate_left.png b/games/static/img/navigate_left.png deleted file mode 100644 index 7b163d6087eb6e7125212131493a77d8fd54c3f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 309 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%1|*NXY)uAIoCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{wYrv$B+ufx04OI4jG8F{a>LTu&ew+K#zFA zYV{8sUF=04n7(ny9lUqIljY#D)vo#q67fbaE+}?}yqUmX$I<`)BX2)Lqr>h8PU#P$ z-aoM3HibudW0~gn2i)RO8(2abj~6!|yU3&`^5aL7Mdw8edB=-hjekEJ_T(yNcG@WW z#PiVPbz2Mfdwp3f)N}Tr!BK^z9ii0~3DYG4ODnn$1PL`xntUenu5Xa|*)wMXpXjaZ z4w|Jg>B%``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{w7Zs$B+ufx0n67njAzLKAwNYbah$kY&K7E z-QB+ReiwM(s7y6q>Fp9Q;jOBb!;c`2!cc$bBE$WydCxu?p5ckO_FppJZEcVI62ZhL zxhoeejsD8xs3h|&)Y)pC^^u Date: Thu, 13 Jul 2023 16:46:27 -0400 Subject: [PATCH 4/5] feat: matching student view --- .gitignore | 5 + games/__pycache__/games.cpython-38.pyc | Bin 6176 -> 9741 bytes games/games.py | 288 ++++++++++++++++---- games/static/css/games.css | 357 +++++++++++++++++++++++-- games/static/html/games.html | 1 + games/static/js/src/games.js | 353 ++++++++++++++++++++++-- 6 files changed, 909 insertions(+), 95 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ecc7871 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__ + +*.lcov + +*.pyc \ No newline at end of file diff --git a/games/__pycache__/games.cpython-38.pyc b/games/__pycache__/games.cpython-38.pyc index 773f72d65e90c34ff3f185e3aabba97d0ffcd783..2d982eda6c4203b1850fe3ff691b13c14977f734 100644 GIT binary patch literal 9741 zcmcIq%X1q?dY=~tAP7>F^?ukM%d#a{5=hCG^;pGg$+RBXm3AY+-YvxO)DYbuh6FHh z&w!GqsI62Cd(1`qfsoKi`_~*IG5Mw|Wb$1s-RywRp=At|&h7=WH88_pNDahIjdni zY12^Cza--1}=d<8Xo#8OedSG-o__lf;Q{(v}GWyu4X!`K^kG?D*YsJYj}-Oim_Az24W^G3w5~s1tZ$j?U*EBd6)t^4 zoEI0K8Lc9g;?gtoso8p)>3ia`xWe>Frhg%>ik~q34%5FB z*Tf$%{VvmgBt8;9W%?A;_r=HJI@70_ejsj$PnfPV-4v_hQ>Ncz`ZIA;++z9+)1Qmm z;ttbiv$d!+{wo$jUvXGr%)Jw8b%+~F6Wp9{O z`s0B=qk%U=tOMgRAChhudwKv5IVIy^G>F4BOie64eX7$7;%+-oB0lAz;M91mQ(QlY z9@ZP_A9s^v7++mpCjNy{BTj;K*^uHa`jqHN$6`hAH>g$YMFUAf=mc0!GyqC7j}sZQmJdg2 z7{xL^>%|e*ll(haJ(M6wyfC3@M?K-SrAmTu(0lAX43r8;%e?_Cp%)Hh1B?BjWv$|A zUc?(u^FzGxG%w+er+FD~Jk2Y3UuoIR^D8ZTm6GQ9^OQTEo}i8N;3&phh*`NOTZf#%g2lVK;1dy+R4Sj*9wv zG@2dEYLAqHA!D;!A?t0UnSR++BZ;n3+XoFsP4fo4)K1WYjTTKNE1zzd+gCAr%r?_V z&(QWqu~esG7$nw_gn*RsKF1cK8{bTCk!$s~0u=^iRxsiT;7~_!C-Pt&CiB#q1&C7sKFz=h%Fx?eQP_KdTBti#2M?+@Tc zeLr&r6i-Mv9|xM#$(&)3bTdZ;N${Paj$wH(K3oP{RJ@!FF-#H-mU9eWj#Yb^G(6T0 zNauCh7(P}9(D@%|leAmIs+sArTa%EWv13g$Jfa>YJj|op$ZQ+W4dTlaD>1jN4QtyF z*4K!Hp4i*=bDL}JZ&7a=pdF^2f-Z@Yt^rrww8QS2Ts8@DsNg7IM;ry@nmn~ifHgD- zI7BASQAu2MXY`?dWT-l7$YTK-JUy5@&Ti80%&<-HzkD7P+F+2E6K+ zyN!)l(b=Z%GD{UHQ|7{^p>|n05W+{#Y>_e>!B!BrRX9wt1@@akFHEJs5!W4_H7j%A z^av*eT1)11aL8aO>*P2MUCVds)876OC2h0aufu!?qNg!eJ{nR; zMp!{ws7)bEV1^ue>8Psc`r@5W@87>l@Yrc21TBLo(Le%|2d0FBEig-D)rq@!ar%>9 z2vn5;ET3X1QUfKoWT3%v0z+yMdwX%_E(L13IYSjNqs!|<_^E)()4*kCC0=JXFQgV3 z5Iu8fX?07T#dzvHB)Q9(6k5BXx#q$u_5?Hm}*?6D$4&Rq<;`jFMwkO&5=2 zIi_VgKct7IMK{%FMs6fSQZu%{tH=m*+wdVZq&PMs#j^n^`LMN(lCU8?oLxY>Tw4|; zlvhH+F*m(l2w_Hk?H~s+hjZZ5*gZ}x!d8P~`!lxM z=|NjaKZ?!ep*+Rr959W~X+?j+=B*IH>=uHDU`n0n8cLD8>Q+|L$21(u9wllIRqUk- zTYW(JpHRZw-;L*sw5SQUj}x=WPmwc=~ubk?iT7?BHmqJQc2Y zlln|3TMXH3&Cwme9Rf($33@O_e@{oiDM>n$JcP|?N|sbFX$g~XGaJ~fWz9ymo2=(( ztv;>1PGZin6)`1(5_`#P*!%Hz&Gf*uz>7XJvUV{g*(BT41&l@)xX+9ywjh6IXkLdT zNsIF&J>q{UfgIScIr|NPE8bP(PyXqNvu!^$o|Lx9-<3CA^|_|MS|$2TSR?b= zasRKbavNh~0dMS_*Trq<2KbvYeENKPsZfL&-I~BE`&o*N*Wsoo*53x_XMSZ+5Aehk z;Uo|S9fmgS#6ehL3Hxj7TZH+T(z&PJL5uH6e7F)fH*p(o1DrEwBMJwZi;EHw^%3B9 zBY;kfSV4lg4O57mJ-ttDN?75#%$CMW_s7Gz!4TJQB0D}^^U`9Dy`q-F zRo+c@aQ7Ar-#EF@nP)0?x=U*^!&YrH2)`Oh&H5?fuw3@-!txs=9ttZAx8msF3gMM~ z&?3yQm^Gk2QozAsYsuViy*b>6^bps54+;VBY9H31N z&?qC&M|eyQw{EB5?#3}1;#jT2EWS9(uMO!I4EHZMhTv#{qv-jSl@;8itUbm}mltdV zkGPspm`o|gad2WztYG9vad;U?)(M;*3f0&(R;lTA_`C+v)tGF`D7)t3j+5PV!x^@a z(JtU&w)`cAfWKs4c?dX4tkBdE1NcbH;W|*I!qbsY{Zl^Y;hPiT9opHVgdAa|EnLDn zpqmZMA&jGXs&VlU22vT}Wfh2o?+wUd9L#_qc$~)!e^2@6^WPfyT!$}JsB?eRX!&Z6 z7@O9gnsZ*33FLeV0gKog{73-)^`aeZ8TR0{vc?Z)a907z0}i1t6UV;I#Hk zk%SzrrKti&z2?n(^;&CBCsa7H<*SJXtqRV_e6Oh}2+qp5iQ2+|S&66DSVu576-E4# z$X`%G7^&@~rhY~F&nY3*Rre`jq}Z6!;NAgaw3^mpu?e5y@CJ4B zDcNyISkil#p!9FZ_~_(hlxyaqxo9r}#OUvAsml4Yh;2Y|ihAS_)E=J!FlNhS$d}Bt z2y1_XX{L|yd5DA1(=S3C7^)2;BHDf8;LKUVLnepNXS1Tdzy*zqr$k2Plc_l{S68?k z$Z{`)uftchz*SDS(36?40S<^y7qePNs@9JB!$4`!%&+VTwl5R6+kZj&a1u?8QsLUH3T_VIx*&OSU@I;=e1Lq+^w>f1eE@c4=QX@gE!sT;q8^}jq|wSolGQfiXyE5}xT-zZ_6n>i78sFm`>HAyHC^l!7UfL zie*zCfB)%l>Hl-?Z>?3V;IG{~76Q~AYjJ5EuPmF?{+_vf-UF3VbG$vx5)inor*j=pU5g#yF=xP0Z*2&;Ur4TI>sX z34c(I6>A=;s#aRoH`Ytms-IEC9ZKjjXE*s~m227>K7x3SnC>GTiuw`vdv*nvt^W;R Cx8S1y delta 1944 zcma)6&2JM&6yLFT{jqkOKt2p1g(VQc1d?DrS|B7CVgkZ3sBx6yin@+x9B;69)0s^P z77_=-r4@&CE*y%)t!kyJs&Z+n_S!$7y=^Z&RISurxb;$`zBdjeL>#)-{5-$6Z)fJc z-<$V1_iQ9F)7EBc@O;wq1wA{mn&=~i)xpe8qD^WvM8hi@4Kw3muoz$cY?>IWe;n?s zp!?RR1IR@f;ybc7Ok(8005cXM^a#}-2aDZo2W|0`;yv^z9bD0hNjgrC(c>$mxQp#x z=%p9wFdbPT4@q&4;u(5^rWNl~{1QD$PbvPE;+N@ZdPecRiciqD={t(=ODjW`o~7rM z!BYGRJx@m!-_QE#1v<8(!Da_&d|FFQ%GKcWc+;FLH<(qhy*iVh>NjI%wO$srN|{r! z_PgE)OyvsMshd{S<<`~0RL(kX&0fpptlYKh+1xEFpUqBKg&Wr8tC{>{)|$PV&07;Q zQ&YFBiA*75U7NP@H>}(Zc=xU~Go77~kHWv?o5Qm;=G7RtJl7JnrE0a#ELh)hy&>xg zOg4{TxlaAwcr(4_1Lls4N!=e_Wz1$)pZF1#hm9Iy1T&F(%0i--eg zhDkcq&m^+Z_N&3$z$goeqoyBel)XyLcIM^7L~eW#>^udK(m8I*cLVq#0m0hK{3u$s z?HFUri)DwF=0T6IWTJf{Yk=2f7~&MbeQf~;*{x9+TUg*V7E~-i_0M(C*k}&PkCN5{ zTyw~@HXfe9?b{KsjNyc@8~j+^7G9~&oO!R73i>)y-&981p=_BC!Dn9vHv{9xaItLx znZhe`592w^zK8ch-MLVL;7|Xz*@KlQ-n&$HD|d#JuN-aPcf_MBTz%me6k6ddXysb1SaDqqJ$T()-Z}%8)lxzVK3L8p>kG%BYCvdj?xRtsq zm`eXo@OndU`K;rI+XLv1O7lYqhY^kd_+bb@l;>HBaNKgM)~%GBM%Qf{#i=b8>*af; z8mlk}s+}@gd*L>I5K5!9D;B~V zvl(54w3NIh?mXAz-`(RwLGVP0R6*-7u{F?<m3i1^NcJUC}8&=s%r42#bPf|M%}8 z@0Nc5xd&KYenc?OgL^KN<{Wd`N(M&7FCbv0t4q?7C5#lM@}or#FZ?VFiVGwg9Z3Vyxnn2@stGUWANSvv9;P+jJ6dhY{NtFZnLu(Eu^` psFhM}9JS@0a$(@ZkvHt;M@AbiU8=J&tOL~z^gx8fNj%sW_z$~Dv3LLg diff --git a/games/games.py b/games/games.py index ea83095..e86677c 100644 --- a/games/games.py +++ b/games/games.py @@ -1,11 +1,14 @@ """An XBlock providing gamification capabilities.""" - import pkg_resources from web_fragments.fragment import Fragment from xblock.core import XBlock #May need to import more or less field types later (https://github.com/openedx/XBlock/blob/master/xblock/fields.py) -from xblock.fields import Integer, Scope, String, Boolean, List#, Dict +from xblock.fields import Integer, Scope, String, Boolean, List, Dict + +#need these libraries for random string generation +import string +import random class GamesXBlock(XBlock): """ @@ -17,21 +20,21 @@ class GamesXBlock(XBlock): The editor view will allow course authors to create and manipulate the games. """ - #Universal fields-------------------------------------------------------------- + #Universal fields------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ title = String( - default="Game Title", + default="Matching", scope=Scope.content, help="The title of the block to be displayed in the xblock." ) - #Change default to 'matching' for matching game and 'flashcards' for flashcards game + #Change default to 'matching' for matching game and 'flashcards' for flashcards game to test type = String( - default="flashcards", + default="matching", scope=Scope.settings, help="The kind of game this xblock is responsible for ('flashcards' or 'matching' for now)." ) - #Matching and flashcards will use the same list, but term_image and definition_image will only be used in flashcards for now + #Matching and flashcards will use the same list, but term_image and definition_image will only be used in flashcards #DUMMY DATA list = List( default=[ @@ -100,7 +103,7 @@ class GamesXBlock(XBlock): 'definition_image': None, 'term': 'T11', 'definition': 'D11' - }, + } ], scope=Scope.content, help="The list of terms and definitions." @@ -109,10 +112,10 @@ class GamesXBlock(XBlock): list_length = Integer( default=len(list.default), scope=Scope.content, - help="TEMP for HTML - WILL LIKELY NEED TO CHANGE WHEN DUMMY DATA IS NO LONGER USED" + help="A field for the length of the list for convenience." ) - #Flashcard game fields-------------------------------------------------------------- + #Flashcard game fields------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ list_index = Integer( default=0, scope=Scope.settings, @@ -125,18 +128,69 @@ class GamesXBlock(XBlock): help="True when the term is visible and false when the definition is visible in the flashcards game." ) - #Matching game fields-------------------------------------------------------------- + #Matching game fields------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ best_time = Integer( default=None, scope=Scope.user_info, help="The user's best time for the matching game." ) - #need field for selected containers - probably a dictionary of two indices referring to the list field's values - #if the dictionary of indices doesn't work, two separate fields is also viable - #may need field for page number if list_length field is not enough - #field for current time likely necessary, will need ot look up how to use it in real-time - #Following fields for editor only-------------------------------------------------------------- + game_started = Boolean( + default = False, + scope=Scope.settings, + help="Bool variable to allow the timer to start from 0 after the game starts." + ) + + time_seconds = Integer( + default=0, + scope=Scope.user_info, + help="The current time elapsed in seconds since starting the matching game." + ) + + selected_containers = Dict( + default={}, + scope=Scope.settings, + help="A dictionary to keep track of selected containers for the matching game." + ) + + matching_id_list = List( + default=[], + scope=Scope.settings, + help="A list of all the matching game ids." + ) + + matching_id_dictionary_index = Dict( + default={}, + scope=Scope.settings, + help="A dictionary to encrypt the ids of the terms and definitions for the matching game." + ) + + matching_id_dictionary_type = Dict( + default={}, + scope=Scope.settings, + help="A dictionary to tie the id to the type of container (term or definition) for the matching game." + ) + + matching_id_dictionary = Dict( + default={}, + scope=Scope.settings, + help="A dictionary to encrypt the ids of the terms and definitions for the matching game." + ) + + match_count = Integer( + default=0, + scope=Scope.settings, + help="Tracks how many matches have been successfully made. Used to determine when to switch pages." + ) + + matches_remaining = Integer( + default = len(list.default), + scope=Scope.content, + help = "The number of matches that remain in the list." + ) + + ''' + #Following fields for editor only------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ shuffle = Boolean( default=True, scope=Scope.settings, @@ -147,8 +201,9 @@ class GamesXBlock(XBlock): scope=Scope.settings, help="Whether to enable the timer for the matching game." ) + ''' - #Important functions (unmodified)-------------------------------------------------------------- + #Important functions (unmodified from xblock installation)------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ def resource_string(self, path): """Handy helper for getting resources from our kit.""" data = pkg_resources.resource_string(__name__, path) @@ -164,9 +219,10 @@ def student_view(self, context=None): frag.add_css(self.resource_string("static/css/games.css")) frag.add_javascript(self.resource_string("static/js/src/games.js")) frag.initialize_js('GamesXBlock') + #frag.initialize_js('FlashcardsXBlock') return frag - #Universal handlers-------------------------------------------------------------- + #Universal handlers------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @XBlock.json_handler def expand_game(self, data, suffix=''): """ @@ -182,24 +238,18 @@ def expand_game(self, data, suffix=''): 'description': description, 'type': self.type } - - @XBlock.json_handler - def start_game(self, data, suffix=''): - """ - A handler to begin the game. - """ - return { - 'term_image': self.list[self.list_index]['term_image'], - 'definition_image': self.list[self.list_index]['definition_image'], - 'term': self.list[self.list_index]['term'], - 'list_length': self.list_length, - } @XBlock.json_handler def close_game(self, data, suffix=''): """ A handler to close the game to its title block. """ + + self.game_started = False + self.time_seconds = 0 + self.match_count = 0 + self.matches_remaining = self.list_length + if self.type == "flashcards": self.term_is_visible=True self.list_index=0 @@ -219,22 +269,29 @@ def display_help(self, data, suffix=''): message = "Match each term with the correct definition" return {'message': message} + #Flashcards handlers------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + @XBlock.json_handler + def start_game_flashcards(self, data, suffix=''): + """ + A handler to begin the flashcards game. + """ + return { + 'list': self.list, + 'list_index': self.list_index, + 'list_length': self.list_length + } - #Flashcards handlers-------------------------------------------------------------- @XBlock.json_handler def flip_flashcard(self, data, suffix=''): """ A handler to flip the flashcard from term to definition and vice versa. """ - - #flip term_is_visible first to show definition - self.term_is_visible = not(self.term_is_visible) - - #term_is_visible has already been flipped, so the conditional evaluates the original value by flipping it again - #When reviewing this logic, pretend that the not() function below is not there since term_is_visible was already flipped once above - if not(self.term_is_visible): + if self.term_is_visible: + self.term_is_visible = not(self.term_is_visible) return {'image': self.list[self.list_index]['definition_image'], 'text': self.list[self.list_index]['definition']} + + self.term_is_visible = not(self.term_is_visible) return {'image': self.list[self.list_index]['term_image'], 'text': self.list[self.list_index]['term']} @XBlock.json_handler @@ -248,30 +305,153 @@ def page_turn(self, data, suffix=''): if data['nextIndex'] == 'left': if self.list_index>0: self.list_index-=1 - #if the current index is 0, circulate to the last flashcard + #else if the current index is 0, circulate to the last flashcard else: self.list_index=len(self.list)-1 return {'term_image': self.list[self.list_index]['term_image'], 'term': self.list[self.list_index]['term'], 'index': self.list_index+1, 'list_length': self.list_length} - #data['nextIndex'] == 'right' here + #else data['nextIndex'] == 'right' if self.list_index """), - ("gamesblock", + ("games", """ """) ] + + + + + + + + """ @XBlock.json_handler def flip_timer(self, data, suffix=''): diff --git a/games/static/css/games.css b/games/static/css/games.css index 6643fd7..559b253 100644 --- a/games/static/css/games.css +++ b/games/static/css/games.css @@ -1,6 +1,6 @@ /* CSS for GamesXBlock */ -/*Universal styles--------------------------------------------------------------*/ +/*Universal styles------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ .title-initial { display: flex; width: 649px; @@ -30,6 +30,13 @@ box-shadow: 0px 8px 48px 0px rgba(0, 0, 0, 0.08), 0px 20px 40px 0px rgba(0, 0, 0, 0.08); } +.game-top { + display: flex; + justify-content: space-between; + align-items: flex-start; + align-self: stretch; +} + .title-persistent { display: flex; align-items: center; @@ -86,6 +93,16 @@ border-radius: 8px; } +.game-description { + color: var(--primary-500, #00262B); + text-align: center; + font-size: 20px; + font-family: Inter; + font-style: normal; + font-weight: 500; + line-height: 28px; +} + .start-button-flashcards, .start-button-matching { display: flex; padding: 10px 16px; @@ -105,6 +122,18 @@ cursor: pointer; } +.flashcard-footer, .matching-footer { + display: flex; + justify-content: space-between; + align-items: center; + align-self: stretch; +} + +.spacer { + width: 24px; + height: 24px; +} + .help { display: flex; flex-direction: column; @@ -156,24 +185,19 @@ background: var(--core-elm, #00262B); } -/*Flashcard styles--------------------------------------------------------------*/ -.flashcard-top { - display: flex; - justify-content: space-between; - align-items: flex-start; - align-self: stretch; +@keyframes confetti { + 0% { transform: translate(0, 0); opacity: 1; } + 100% { transform: translate(0vw, 200vh) rotate(720deg); opacity: 0; } } -.flashcard-description { - color: var(--primary-500, #00262B); - text-align: center; - font-size: 20px; - font-family: Inter; - font-style: normal; - font-weight: 500; - line-height: 28px; +.confetti { + position: fixed; + top: 0; + pointer-events: none; + opacity: 0; } +/*Flashcard styles------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ .flashcard-block { display: flex; height: 373px; @@ -210,18 +234,6 @@ line-height: 28px; } -.flashcard-footer { - display: flex; - justify-content: space-between; - align-items: center; - align-self: stretch; -} - -.spacer { - width: 24px; - height: 24px; -} - .flashcard-navigation { display: flex; align-items: center; @@ -261,4 +273,293 @@ line-height: normal; } -/*Matching styles--------------------------------------------------------------*/ +/*Matching styles------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ +.matching-block { + display: flex; + height: 610px; + align-items: flex-start; + gap: 8px; + align-self: stretch; +} + +.matching-column-l, .matching-column-r { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + flex: 1 0 0; + align-self: stretch; +} + +@keyframes incorrect { + 0% {border: 2px solid var(--danger-500, #AB0D02); background-color: #FCF1F4}; + 100% {border: initial; background-color: initial}; +} + +.matching-container, .matching-container-empty { + display: flex; + padding: 16px; + justify-content: center; + align-items: center; + gap: 10px; + flex: 1 0 0; + align-self: stretch; + + flex-direction: column; + + border-radius: 8px; + border: 2px solid var(--light-300, #F2F0EF); + + color: var(--primary-500, #00262B); + text-align: center; + /* + leading-trim: both; + text-edge: cap; + */ + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + + animation-duration: 1.3s; + + cursor: pointer; +} + +@keyframes correct { + 0% {border: 2px solid var(--success-500, #0D7D4D); background-color: var(--success-100, #F2FAF7); color: var(--primary-500, #00262B)}; + 100% {border: initial; background-color: initial; color: initial};; +} + +.matching-container-empty { + cursor: initial; + color: white; + user-select: none; + border: 2px solid #ffffff; +} + +.matching-progress-container { + display: flex; + width: 48px; + height: 48px; + border-radius: 4px; + background: #fff; + flex-direction: column; + align-items: center; +} + +.matching-progress-indicator { + position: relative; + height: 48px; + width: 48px; + border-radius: 50%; + background: conic-gradient(#0d7d4d 0deg, #f2f0ef 0deg); + display: flex; + align-items: center; + justify-content: center; +} +.matching-progress-indicator::before { + content: ""; + position: absolute; + height: 40px; + width: 40px; + border-radius: 50%; + background-color: #fff; +} + +.matching-progress-text { + position: relative; + color: var(--primary-500, #00262B); + text-align: center; + /* + leading-trim: both; + text-edge: cap; + */ + font-family: Inter; + font-size: 17px; + font-style: normal; + font-weight: 500; + line-height: 28px; + letter-spacing: -1.19px; +} + +.matching-timer { + color: var(--primary-500, #00262B); + text-align: center; + /* + leading-trim: both; + text-edge: cap; + */ + font-family: Inter; + font-size: 28px; + font-style: normal; + font-weight: 500; + line-height: 28px; + letter-spacing: -1.96px; +} + +.matching-end-block { + display: flex; + padding: 0px 77px; + flex-direction: column; + justify-content: center; + align-items: center; + flex: 1 0 0; + align-self: stretch; +} + +.matching-end-congratulations { + color: var(--primary-700, #002121); + text-align: center; + /* + leading-trim: both; + text-edge: cap; + */ + font-family: Inter; + font-size: 32px; + font-style: normal; + font-weight: 700; + line-height: 36px; /* 112.5% */ +} + +.matching-replay-button { + display: flex; + padding: 10px 16px; + justify-content: center; + align-items: center; + gap: 8px; + + background: var(--primary-500, #00262B); + + color: #FFF; + font-family: Inter; + font-size: 18px; + font-style: normal; + font-weight: 500; + line-height: 24px; /* 133.333% */ + + cursor: pointer; +} + +.matching-end-standard-text-block { + display: flex; + height: 610px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 32px; +} + +.matching-end-standard-text, .matching-end-standard-time { + color: var(--primary-500, #00262B); + text-align: center; + /* + leading-trim: both; + text-edge: cap; + */ + font-family: Inter; + font-size: 22px; + font-style: normal; + font-weight: 500; + line-height: 28px; /* 127.273% */ +} + +.matching-end-standard-time { + font-weight: 700; +} + +.matching-end-standard-best-block { + display: flex; + padding: 19px 24px; + justify-content: center; + align-items: center; + gap: 10px; + + border-radius: 8px; + background: rgba(240, 204, 0, 0.20); +} + +.matching-standard-award { + width: 28px; + height: 28px; +} + +.matching-end-standard-best-text, .matching-end-standard-best-time { + color: var(--extras-black, #000); + text-align: center; + /* + leading-trim: both; + text-edge: cap; + */ + font-family: Inter; + font-size: 22px; + font-style: normal; + font-weight: 700; + line-height: normal; +} + +.matching-end-standard-best-time { + text-align: right; +} + +.matching-record-text-block { + display: inline-flex; + height: 610px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 32px; +} + +.matching-end-record-time, .matching-end-record-message { + color: var(--primary-500, #00262B); + text-align: center; + /* + leading-trim: both; + text-edge: cap; + */ + font-family: Inter; + font-size: 88px; + font-style: normal; + font-weight: 600; + line-height: 28px; /* 31.818% */ +} + +.matching-end-record-message { + font-size: 28px; + font-weight: 700; +} + +.matching-end-record-previous-block { + display: flex; + padding: 16px; + justify-content: center; + align-items: center; + gap: 10px; + + border-radius: 8px; + background: rgba(242, 240, 239, 0.60); +} + +.matching-end-record-previous-text, .matching-end-record-previous-time { + color: var(--gray-700, #454545); + /* + leading-trim: both; + text-edge: cap; + */ + font-family: Inter; + font-size: 18px; + font-style: normal; + font-weight: 600; + line-height: normal; +} + +.matching-end-record-previous-time { + text-align: right; +} + +.matching-end-record-confetti-block { + width: 616.382px; + height: 685.116px; +} \ No newline at end of file diff --git a/games/static/html/games.html b/games/static/html/games.html index 080552c..f0e035d 100644 --- a/games/static/html/games.html +++ b/games/static/html/games.html @@ -1,3 +1,4 @@ +

{self.title}
diff --git a/games/static/js/src/games.js b/games/static/js/src/games.js index cc12cc0..0c40172 100644 --- a/games/static/js/src/games.js +++ b/games/static/js/src/games.js @@ -1,19 +1,19 @@ /* Javascript for GamesXBlock. */ function GamesXBlock(runtime, element) { - //Universal functions-------------------------------------------------------------- + //Universal functions------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ //Function to expand the game from its title block function expandGame(fullView) { $('.title-initial', element).remove(); var expandedBlock = "
"; - var topDiv = "
"; + var topDiv = "
"; var title = "
"; var closeButton = "
"; var closeBackground = "
"; var closeImage = "" var startBlock = "
"; - var description = "
"; + var description = "
"; switch(fullView.type){ case 'flashcards': var startButton = "
Start
"; break; @@ -23,14 +23,14 @@ function GamesXBlock(runtime, element) { $('.gamesxblock', element).append(expandedBlock); $('.background-block', element).append(topDiv); - $('.flashcard-top', element).append(title); + $('.game-top', element).append(title); $('.title-persistent', element).text(fullView.title); - $('.flashcard-top', element).append(closeButton); + $('.game-top', element).append(closeButton); $('.close-button', element).append(closeBackground); $('.close-background', element).append(closeImage); $('.background-block', element).append(startBlock); $('.start-block', element).append(description); - $('.flashcard-description', element).text(fullView.description); + $('.game-description', element).text(fullView.description); $('.start-block', element).append(startButton); } @@ -93,7 +93,7 @@ function GamesXBlock(runtime, element) { }); }); - //Flashcards functions-------------------------------------------------------------- + //Flashcards functions------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ //Function to start the flashcards game function startFlashcards(firstCard) { $('.start-block', element).remove(); @@ -114,9 +114,9 @@ function GamesXBlock(runtime, element) { $('.background-block', element).append(flashcardBlock); $('.flashcard-block', element).append(firstImage); - $('.image', element).attr("src", firstCard.term_image); + $('.image', element).attr("src", firstCard.list[firstCard.list_index]['term_image']); $('.flashcard-block', element).append(text); - $('.flashcard-text', element).text(firstCard.term); + $('.flashcard-text', element).text(firstCard.list[firstCard.list_index]['term']); $('.background-block', element).append(footer); $('.flashcard-footer', element).append(spacer); $('.flashcard-footer', element).append(navigation); @@ -133,7 +133,7 @@ function GamesXBlock(runtime, element) { $(document).on('click', '.start-button-flashcards', function(eventObject) { $.ajax({ type: "POST", - url: runtime.handlerUrl(element, 'start_game'), + url: runtime.handlerUrl(element, 'start_game_flashcards'), data: JSON.stringify({}), success: startFlashcards }); @@ -179,35 +179,354 @@ function GamesXBlock(runtime, element) { }); }); - //Matching functions-------------------------------------------------------------- + //Matching functions------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ //Function to start the matching game function startMatching(firstPage) { $('.start-block', element).remove(); + + //the css style changes made by jquery are not persistent when the background-block element is removed, so the following line does not need to be undone when closeGame is called + $('.background-block', element).css({"height":"initial", "padding":"20px 24px 24px 24px"}); + + var matchingBlock = "
"; + var matchingColumnL = "
" + var matchingColumnR = "
"; + + $('.background-block', element).append(matchingBlock); + $('.matching-block', element).append(matchingColumnL); + $('.matching-block', element).append(matchingColumnR); + + //progress shown by default, but if there are 5 or less pairs, it will not be displayed + var showProgress = true; + + for (let i=0; i<5; i++) { + if (i>2*firstPage.list_length-1) { + showProgress = false; + var emptyContainer = "
"; + $('.matching-column-l', element).append(emptyContainer); + continue; + } + var id = firstPage.id_list[i]; + var content = firstPage.id_dictionary[id] + var container = "
" + content + "
"; + $('.matching-column-l', element).append(container); + } + for (let i=5; i<10; i++) { + if (i>2*firstPage.list_length-1) { + showProgress = false; + var emptyContainer = "
"; + $('.matching-column-r', element).append(emptyContainer); + continue; + } + var id = firstPage.id_list[i]; + var content = firstPage.id_dictionary[id] + var container = "
" + content + "
"; + $('.matching-column-r', element).append(container); + } + + var footer = ""; + + var progressContainer = "
"; + var progressIndicator = "
"; + var progressText = "
"; + + var timer = "
" + firstPage.time + "
"; + var help = "
"; + var helpOutline = "
"; + + $('.background-block', element).append(footer); + + $('.matching-footer', element).append(progressContainer); + if(showProgress) { + var pageCount = Math.floor((firstPage.list_length-1)/5)+1; + var currentProgressDeg = 360*(1/pageCount); + var progressIndicatorStyle = "conic-gradient(#0d7d4d " + currentProgressDeg + "deg, #f2f0ef 0deg"; + $('.matching-progress-container', element).append(progressIndicator); + $('.matching-progress-indicator', element).append(progressText); + $('.matching-progress-indicator', element).css("background", progressIndicatorStyle) + $('.matching-progress-text', element).text("1 " + "/" + " " + pageCount); + } + + $('.matching-footer', element).append(timer); + $('.matching-footer', element).append(help); + $('.help', element).append(helpOutline); } $(document).on('click', '.start-button-matching', function(eventObject) { $.ajax({ type: "POST", - url: runtime.handlerUrl(element, 'start_game'), + url: runtime.handlerUrl(element, 'start_game_matching'), data: JSON.stringify({}), success: startMatching }); }); - /* + + //Timer + function formatTime(timeSeconds) { + var seconds = ":" + timeSeconds%60; + if (timeSeconds%60 < 10) + var seconds = ":0" + timeSeconds%60; + + var minutes = Math.floor(timeSeconds/60)%60; + + var hours = ""; + if (timeSeconds >= 3600) { + var hours = Math.floor(timeSeconds/3600)%24 + ":"; + if (Math.floor(timeSeconds/60)%60 < 10) + hours = Math.floor(timeSeconds/3600)%24 + ":0"; + } + + //returns in (HH:M)M:SS format, where (HH:M) only appear if they are relevant + return hours + minutes + seconds; + } + + function updateTimer(newTime) { + //do nothing until the game starts + if (!newTime.game_started) + return; + $('.matching-timer', element).text(formatTime(newTime.value)); + } + + $(document).ready(function() { + setInterval(function() { + $.ajax({ + type: "POST", + url: runtime.handlerUrl(element, 'update_timer'), + data: JSON.stringify({}), + cache: false, + success: updateTimer + }); + }, 1000); + }); + //Function to select a container displaying either a term or a definition function selectContainer(selected) { + //first selection + if (selected.first_selection) { + //cancel all animations if another container is selected in case they are not finished yet + $('.matching-container', element).css("animation-name", "initial"); + + $('.matching-container', element).css("border", "2px solid var(--light-300, #F2F0EF)"); + $('.matching-container', element).css("background-color", "initial"); + $(selected.id).css("border", "2px solid var(--primary-500, #00262B)"); + return; + } + + //deselect + if (selected.deselect) { + $(selected.id).css("border", "2px solid var(--light-300, #F2F0EF)"); + $(selected.id).css("background-color", "initial"); + return; + } + + //false match + if (!selected.match) { + //reset the previously selected element's border before the animation + $(selected.prev_id).css("border", "2px solid var(--light-300, #F2F0EF)"); + $(selected.id).css({"animation-name": "incorrect"}); + $(selected.prev_id).css({"animation-name": "incorrect"}); + + var div = document.getElementById((selected.id).substring(1)); + var prev_div = document.getElementById((selected.prev_id).substring(1)); + + //make sure event listeners are being removed + div.addEventListener("animationend", function(){$(selected.id).css({"animation-name": "initial"})}); + prev_div.addEventListener("animationend", function(){$(selected.prev_id).css({"animation-name": "initial"})}); + return; + } + + //match (hide correct pairs with another class) + $(selected.id).css("border", "2px solid #ffffff"); + $(selected.prev_id).css("border", "2px solid #ffffff"); + $(selected.id).removeClass("matching-container"); + $(selected.prev_id).removeClass("matching-container"); + $(selected.id).addClass("matching-container-empty"); + $(selected.prev_id).addClass("matching-container-empty"); + $(selected.id).css({"animation-name": "correct"}); + $(selected.prev_id).css({"animation-name": "correct"}); + + //flip the page every 5 matches + if (selected.match_count%5 == 0 && selected.matches_remaining > 0) { + for(let i=2*selected.match_count-10; i<2*selected.match_count; i++) { + id = "#" + selected.id_list[i]; + $(id).remove(); + } + + for (let i=2*selected.match_count; i<2*selected.match_count+5; i++) { + if (i>2*selected.list_length-1) { + var emptyContainer = "
"; + $('.matching-column-l', element).append(emptyContainer); + continue; + } + var id = selected.id_list[i]; + var content = selected.id_dictionary[id] + var container = "
" + content + "
"; + $('.matching-column-l', element).append(container); + } + for (let i=2*selected.match_count+5; i<2*selected.match_count+10; i++) { + if (i>2*selected.list_length-1) { + var emptyContainer = "
"; + $('.matching-column-r', element).append(emptyContainer); + continue; + } + var id = selected.id_list[i]; + var content = selected.id_dictionary[id] + var container = "
" + content + "
"; + $('.matching-column-r', element).append(container); + } + + //this will only have an effect if the progress indicator was created during the start function, no need to check anything + var currentProgress = Math.floor(selected.match_count/5+1); + var pageCount = Math.floor((selected.list_length-1)/5)+1 + var currentProgressDeg = 360*(currentProgress/pageCount); + var progressIndicatorStyle = "conic-gradient(#0d7d4d " + currentProgressDeg + "deg, #f2f0ef 0deg"; + $('.matching-progress-indicator', element).css("background", progressIndicatorStyle) + $('.matching-progress-text', element).text(currentProgress + " " + "/" + " " + pageCount); + } + //end game after last match is made successfully + if (selected.matches_remaining == 0) { + $.ajax({ + type: "POST", + url: runtime.handlerUrl(element, 'end_game_matching'), + data: JSON.stringify({newTime: selected.time_seconds}), + success: endGameMatching + }); + } } - $(document).on('', '', function(eventObject) { + //function for ajax to end matching game + function endGameMatching(lastPage) { + //page changes regardless of record + $('.matching-block', element).remove(); + $('.matching-footer', element).remove(); + + var endBlock = "
"; + var endFooter = ""; + + $('.background-block', element).append(endBlock); + $('.background-block', element).append(endFooter); + $('.matching-footer', element).css("height", "48px"); + + if (lastPage.new_record) { + var textBlock = "
"; + var congrats = "
Congratulations!
"; + var newTime = "
" + formatTime(lastPage.new_time) + "
"; //need to convert new_time to h:m:s format (maybe make a function for this and update timer function) + var message = "
A new personal best!
"; + if (lastPage.prev_time != null) { + var previousBlock = "
"; + var previousText = "
Previous best:
"; + var previousTime = "
" + formatTime(lastPage.prev_time) + "
"; + } + var replayButton = "
Play again
"; + + $('.matching-end-block', element).append(textBlock); + $('.matching-record-text-block', element).append(congrats); + $('.matching-record-text-block', element).append(newTime); + $('.matching-record-text-block', element).append(message); + if (lastPage.prev_time != null) { + $('.matching-record-text-block', element).append(previousBlock); + $('.matching-end-record-previous-block', element).append(previousText); + $('.matching-end-record-previous-block', element).append(previousTime); + } + $('.matching-record-text-block', element).append(replayButton); + + + + //add a random number of confetti particles at random horizontal positions + for (let i=0; i<5*Math.floor(Math.random())+25; i++) { + let type = Math.floor(9*Math.random()); + let left = Math.floor(80*Math.random()+10); + let duration = 2*Math.random()+5; + switch(type) { + case 0: { + var confetti = "
"; + break; + }; + case 1: { + var confetti = "
"; + break; + }; + case 2: { + var confetti = "
"; + break; + }; + case 3: { + var confetti = "
"; + break; + }; + case 4: { + var confetti = "
"; + break; + }; + case 5: { + var confetti = "
"; + break; + }; + case 6: { + var confetti = "
"; + break; + }; + case 7: { + var confetti = "
"; + break; + }; + case 8: { + var confetti = "
"; + break; + }; + default: { + var confetti = "
"; + }; + } + $('.matching-end-block', element).append(confetti); + } + } + + //else record was not beaten + else { + var textBlock = "
"; + var congrats = "
Congratulations!
"; + var message = "
You completed the matching game in " + "" + formatTime(lastPage.new_time) + "" + "
Keep up the good work!
"; + var bestBlock = "
"; + var award = "
"; + var bestText = "
Your personal best
"; + var bestTime = "
" + formatTime(lastPage.prev_time) + "
"; + var replayButton = "
Play again
"; + + $('.matching-end-block', element).append(textBlock); + $('.matching-end-standard-text-block', element).append(congrats); + $('.matching-end-standard-text-block', element).append(message); + $('.matching-end-standard-text-block', element).append(bestBlock); + $('.matching-end-standard-best-block', element).append(award); + $('.matching-end-standard-best-block', element).append(bestText); + $('.matching-end-standard-best-block', element).append(bestTime); + $('.matching-end-standard-text-block', element).append(replayButton); + } + } + + $(document).on('click', '.matching-container', function(eventObject) { + var containerID = $(this).attr("id"); $.ajax({ type: "POST", - url: runtime.handlerUrl(element, ''), - data: JSON.stringify({}), + url: runtime.handlerUrl(element, 'select_container'), + data: JSON.stringify({id: containerID}), success: selectContainer }); }); - */ + + function resetMatching(initialState) { + $('.gamesxblock', element).empty(); + expandGame(initialState); + } + + $(document).on('click', '.matching-replay-button', function(eventObject) { + $.ajax({ + type: "POST", + url: runtime.handlerUrl(element, 'expand_game'), + data: JSON.stringify({}), + success: resetMatching + }); + }); return {}; From a72d572189333b4d275f1c58638d19711cca9371 Mon Sep 17 00:00:00 2001 From: Taylor Preston Date: Mon, 17 Jul 2023 14:15:05 -0400 Subject: [PATCH 5/5] feat: formatting code for matching student view --- games/__pycache__/__init__.cpython-38.pyc | Bin 189 -> 0 bytes games/__pycache__/games.cpython-38.pyc | Bin 9741 -> 0 bytes games/static/css/games.css | 3 +- games/static/js/src/games.js | 145 ++++++++++++---------- 4 files changed, 83 insertions(+), 65 deletions(-) delete mode 100644 games/__pycache__/__init__.cpython-38.pyc delete mode 100644 games/__pycache__/games.cpython-38.pyc diff --git a/games/__pycache__/__init__.cpython-38.pyc b/games/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 944669fb7967232cdfbe8d5be7a3ddcca75271b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 189 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 Cx8S1y diff --git a/games/static/css/games.css b/games/static/css/games.css index 559b253..530edc8 100644 --- a/games/static/css/games.css +++ b/games/static/css/games.css @@ -3,7 +3,6 @@ /*Universal styles------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ .title-initial { display: flex; - width: 649px; padding: 18px 281px; justify-content: center; align-items: center; @@ -187,7 +186,7 @@ @keyframes confetti { 0% { transform: translate(0, 0); opacity: 1; } - 100% { transform: translate(0vw, 200vh) rotate(720deg); opacity: 0; } + 100% { transform: translate(0vw, 85vh) rotate(720deg); opacity: 0; } } .confetti { diff --git a/games/static/js/src/games.js b/games/static/js/src/games.js index 0c40172..155d50a 100644 --- a/games/static/js/src/games.js +++ b/games/static/js/src/games.js @@ -2,8 +2,10 @@ function GamesXBlock(runtime, element) { //Universal functions------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - //Function to expand the game from its title block + //Expand Game function expandGame(fullView) { + //Function to expand the game from its title block + $('.title-initial', element).remove(); var expandedBlock = "
"; @@ -15,6 +17,7 @@ function GamesXBlock(runtime, element) { var startBlock = "
"; var description = "
"; + //Depending on the game type, add the respective start button; add new cases for new game types switch(fullView.type){ case 'flashcards': var startButton = "
Start
"; break; case 'matching': var startButton = "
Start
"; break; @@ -33,7 +36,6 @@ function GamesXBlock(runtime, element) { $('.game-description', element).text(fullView.description); $('.start-block', element).append(startButton); } - $(document).on('click', '.title-initial', function(eventObject) { $.ajax({ type: "POST", @@ -43,15 +45,16 @@ function GamesXBlock(runtime, element) { }); }); - //Function to close the current game back to the title block + //Close Game function closeGame(initialView) { + //Function to close the current game back to the title block + var init = "
"; $('.background-block', element).remove(); $('.gamesxblock', element).append(init); $('.title-initial', element).text(initialView.title); } - $(document).on('click', '.close-image', function(eventObject) { $.ajax({ type: "POST", @@ -61,8 +64,10 @@ function GamesXBlock(runtime, element) { }); }) - //Function to show and hide the game's tooltip + //Help function getHelp(help) { + //Function to show the game's tooltip + var tooltip = "
"; var tooltipText = "
"; @@ -70,11 +75,11 @@ function GamesXBlock(runtime, element) { $('.tooltip', element).append(tooltipText); $('.tooltip-text', element).text(help.message); } - function hideHelp(help) { + //Function to hide the game's tooltip + $('.tooltip', element).remove(); } - $(document).on('mouseenter', '.help', function(eventObject) { $.ajax({ type: "POST", @@ -83,7 +88,6 @@ function GamesXBlock(runtime, element) { success: getHelp }); }); - $(document).on('mouseleave', '.help', function(eventObject) { $.ajax({ type: "POST", @@ -94,8 +98,10 @@ function GamesXBlock(runtime, element) { }); //Flashcards functions------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - //Function to start the flashcards game + //Start Flashcards function startFlashcards(firstCard) { + //Function to start the flashcards game + $('.start-block', element).remove(); var flashcardBlock = "
"; @@ -129,7 +135,6 @@ function GamesXBlock(runtime, element) { $('.flashcard-navigation', element).append(right); $('.flashcard-right-button', element).append(rightImage); } - $(document).on('click', '.start-button-flashcards', function(eventObject) { $.ajax({ type: "POST", @@ -139,12 +144,13 @@ function GamesXBlock(runtime, element) { }); }); - //Function to flip the current flashcard + //Flip Flashcard function flipFlashcard(newSide) { + //Function to flip the current flashcard + $('.image', element).attr("src", newSide.image); $('.flashcard-text', element).text(newSide.text); } - $(document).on('click', '.flashcard-block', function(eventObject) { $.ajax({ type: "POST", @@ -154,13 +160,14 @@ function GamesXBlock(runtime, element) { }); }); - //Function to turn the page to another flashcard + //Turn Page function pageTurn(nextCard) { + //Function to turn the page to another flashcard + $('.image', element).attr("src", nextCard.term_image); $('.flashcard-text', element).text(nextCard.term); $('.flashcard-navigation-text', element).text(nextCard.index + " / " + nextCard.list_length); } - $(document).on('click', '.flashcard-left-button', function(eventObject) { $.ajax({ type: "POST", @@ -169,7 +176,6 @@ function GamesXBlock(runtime, element) { success: pageTurn }); }); - $(document).on('click', '.flashcard-right-button', function(eventObject) { $.ajax({ type: "POST", @@ -180,11 +186,13 @@ function GamesXBlock(runtime, element) { }); //Matching functions------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - //Function to start the matching game + //Start Matching function startMatching(firstPage) { + //Function to start the matching game + $('.start-block', element).remove(); - //the css style changes made by jquery are not persistent when the background-block element is removed, so the following line does not need to be undone when closeGame is called + //the css style changes made by jquery are not persistent after the element is removed, so the following line does not need to be undone when closeGame is called $('.background-block', element).css({"height":"initial", "padding":"20px 24px 24px 24px"}); var matchingBlock = "
"; @@ -198,7 +206,9 @@ function GamesXBlock(runtime, element) { //progress shown by default, but if there are 5 or less pairs, it will not be displayed var showProgress = true; + //add the first five items to the left column for (let i=0; i<5; i++) { + //if there are no more terms to add, use empty containers instead to account for formatting if (i>2*firstPage.list_length-1) { showProgress = false; var emptyContainer = "
"; @@ -210,7 +220,9 @@ function GamesXBlock(runtime, element) { var container = "
" + content + "
"; $('.matching-column-l', element).append(container); } + //add the next five items to the right column for (let i=5; i<10; i++) { + //if there are no more terms to add, use empty containers instead to account for formatting if (i>2*firstPage.list_length-1) { showProgress = false; var emptyContainer = "
"; @@ -224,17 +236,15 @@ function GamesXBlock(runtime, element) { } var footer = ""; - var progressContainer = "
"; var progressIndicator = "
"; var progressText = "
"; - var timer = "
" + firstPage.time + "
"; var help = "
"; var helpOutline = "
"; $('.background-block', element).append(footer); - + //add progress container regardless for formatting $('.matching-footer', element).append(progressContainer); if(showProgress) { var pageCount = Math.floor((firstPage.list_length-1)/5)+1; @@ -245,12 +255,10 @@ function GamesXBlock(runtime, element) { $('.matching-progress-indicator', element).css("background", progressIndicatorStyle) $('.matching-progress-text', element).text("1 " + "/" + " " + pageCount); } - $('.matching-footer', element).append(timer); $('.matching-footer', element).append(help); $('.help', element).append(helpOutline); } - $(document).on('click', '.start-button-matching', function(eventObject) { $.ajax({ type: "POST", @@ -262,6 +270,8 @@ function GamesXBlock(runtime, element) { //Timer function formatTime(timeSeconds) { + //Function to format the time_seconds field (for use by other functions) + var seconds = ":" + timeSeconds%60; if (timeSeconds%60 < 10) var seconds = ":0" + timeSeconds%60; @@ -275,18 +285,19 @@ function GamesXBlock(runtime, element) { hours = Math.floor(timeSeconds/3600)%24 + ":0"; } - //returns in (HH:M)M:SS format, where (HH:M) only appear if they are relevant + //returns as a string formatted as (HH:M)M:SS, where (HH:M) only appear if they are relevant return hours + minutes + seconds; } - function updateTimer(newTime) { + //Function to update the timer + //do nothing until the game starts if (!newTime.game_started) return; $('.matching-timer', element).text(formatTime(newTime.value)); } - $(document).ready(function() { + //Update the timer every 1000 ms setInterval(function() { $.ajax({ type: "POST", @@ -298,9 +309,11 @@ function GamesXBlock(runtime, element) { }, 1000); }); - //Function to select a container displaying either a term or a definition + //Select Container function selectContainer(selected) { - //first selection + //Function to handle selecting containers displaying either a term or a definition (also covers matches and deselcting) + + //first selection; when no other container is selected, select the container that was clicked if (selected.first_selection) { //cancel all animations if another container is selected in case they are not finished yet $('.matching-container', element).css("animation-name", "initial"); @@ -311,32 +324,30 @@ function GamesXBlock(runtime, element) { return; } - //deselect + //deselect; when a selected container is selected again, deselect it if (selected.deselect) { $(selected.id).css("border", "2px solid var(--light-300, #F2F0EF)"); $(selected.id).css("background-color", "initial"); return; } - //false match + //false match; if the second container selected does not match the first container, run the 'incorrect' animation on the two selected then deselect them if (!selected.match) { - //reset the previously selected element's border before the animation + //reset the previously selected element's border before the animation so that it does not appear selected after the animation $(selected.prev_id).css("border", "2px solid var(--light-300, #F2F0EF)"); + $(selected.id).css({"animation-name": "incorrect"}); $(selected.prev_id).css({"animation-name": "incorrect"}); - - var div = document.getElementById((selected.id).substring(1)); - var prev_div = document.getElementById((selected.prev_id).substring(1)); - //make sure event listeners are being removed - div.addEventListener("animationend", function(){$(selected.id).css({"animation-name": "initial"})}); - prev_div.addEventListener("animationend", function(){$(selected.prev_id).css({"animation-name": "initial"})}); + //if the animation-name is not reset in the 'first selection' section above, the animations will only be able to fire once return; } - //match (hide correct pairs with another class) + //match; if the second container selected matches the first container, change their classes to 'matching-container-empty' and run the 'correct' animation + //reset the borders to white to prevent the dark border to persist $(selected.id).css("border", "2px solid #ffffff"); $(selected.prev_id).css("border", "2px solid #ffffff"); + //replace the class and apply the correct animation $(selected.id).removeClass("matching-container"); $(selected.prev_id).removeClass("matching-container"); $(selected.id).addClass("matching-container-empty"); @@ -344,14 +355,16 @@ function GamesXBlock(runtime, element) { $(selected.id).css({"animation-name": "correct"}); $(selected.prev_id).css({"animation-name": "correct"}); - //flip the page every 5 matches + //next page; every 5 matches, display the next 5 pairs or whatever remains if (selected.match_count%5 == 0 && selected.matches_remaining > 0) { + //clear the page for(let i=2*selected.match_count-10; i<2*selected.match_count; i++) { id = "#" + selected.id_list[i]; $(id).remove(); } - + //add the next 5 items to the left column for (let i=2*selected.match_count; i<2*selected.match_count+5; i++) { + //if there are no more terms to add, use empty containers instead to account for formatting if (i>2*selected.list_length-1) { var emptyContainer = "
"; $('.matching-column-l', element).append(emptyContainer); @@ -362,7 +375,9 @@ function GamesXBlock(runtime, element) { var container = "
" + content + "
"; $('.matching-column-l', element).append(container); } + //add the next 5 items to the right column for (let i=2*selected.match_count+5; i<2*selected.match_count+10; i++) { + //if there are no more terms to add, use empty containers instead to account for formatting if (i>2*selected.list_length-1) { var emptyContainer = "
"; $('.matching-column-r', element).append(emptyContainer); @@ -374,7 +389,7 @@ function GamesXBlock(runtime, element) { $('.matching-column-r', element).append(container); } - //this will only have an effect if the progress indicator was created during the start function, no need to check anything + //if the progress indicator exists, it will be updated, otherwise this will simply have no effect var currentProgress = Math.floor(selected.match_count/5+1); var pageCount = Math.floor((selected.list_length-1)/5)+1 var currentProgressDeg = 360*(currentProgress/pageCount); @@ -383,7 +398,7 @@ function GamesXBlock(runtime, element) { $('.matching-progress-text', element).text(currentProgress + " " + "/" + " " + pageCount); } - //end game after last match is made successfully + //end game; after the last match is made, end the game with an ajax call if (selected.matches_remaining == 0) { $.ajax({ type: "POST", @@ -394,9 +409,11 @@ function GamesXBlock(runtime, element) { } } - //function for ajax to end matching game + //End Matching Game function endGameMatching(lastPage) { - //page changes regardless of record + //function for ajax to end matching game + + //changes made regardless (new record or not) $('.matching-block', element).remove(); $('.matching-footer', element).remove(); @@ -407,11 +424,13 @@ function GamesXBlock(runtime, element) { $('.background-block', element).append(endFooter); $('.matching-footer', element).css("height", "48px"); + //new record; Show confetti and new best if (lastPage.new_record) { var textBlock = "
"; var congrats = "
Congratulations!
"; - var newTime = "
" + formatTime(lastPage.new_time) + "
"; //need to convert new_time to h:m:s format (maybe make a function for this and update timer function) + var newTime = "
" + formatTime(lastPage.new_time) + "
"; var message = "
A new personal best!
"; + //only display previous time if it exists if (lastPage.prev_time != null) { var previousBlock = "
"; var previousText = "
Previous best:
"; @@ -423,6 +442,7 @@ function GamesXBlock(runtime, element) { $('.matching-record-text-block', element).append(congrats); $('.matching-record-text-block', element).append(newTime); $('.matching-record-text-block', element).append(message); + //only display previous time if it exists if (lastPage.prev_time != null) { $('.matching-record-text-block', element).append(previousBlock); $('.matching-end-record-previous-block', element).append(previousText); @@ -430,59 +450,57 @@ function GamesXBlock(runtime, element) { } $('.matching-record-text-block', element).append(replayButton); - - - //add a random number of confetti particles at random horizontal positions - for (let i=0; i<5*Math.floor(Math.random())+25; i++) { + //add 80 confetti particles between 10% and 90% left values on the screen + for (let i=10; i<=90/*5*Math.floor(Math.random())+25*/; i++) { let type = Math.floor(9*Math.random()); - let left = Math.floor(80*Math.random()+10); - let duration = 2*Math.random()+5; + let left = i; + let duration = 2*Math.random()+1.5; switch(type) { case 0: { - var confetti = "
"; + var confetti = "
"; break; }; case 1: { - var confetti = "
"; + var confetti = "
"; break; }; case 2: { - var confetti = "
"; + var confetti = "
"; break; }; case 3: { - var confetti = "
"; + var confetti = "
"; break; }; case 4: { - var confetti = "
"; + var confetti = "
"; break; }; case 5: { - var confetti = "
"; + var confetti = "
"; break; }; case 6: { - var confetti = "
"; + var confetti = "
"; break; }; case 7: { - var confetti = "
"; + var confetti = "
"; break; }; case 8: { - var confetti = "
"; + var confetti = "
"; break; }; default: { - var confetti = "
"; + var confetti = "
"; }; } $('.matching-end-block', element).append(confetti); } } - //else record was not beaten + //else record was not beaten; display current time and personal best (no need to worry about nulls since this will never be the first end page the user sees) else { var textBlock = "
"; var congrats = "
Congratulations!
"; @@ -503,7 +521,6 @@ function GamesXBlock(runtime, element) { $('.matching-end-standard-text-block', element).append(replayButton); } } - $(document).on('click', '.matching-container', function(eventObject) { var containerID = $(this).attr("id"); $.ajax({ @@ -514,11 +531,13 @@ function GamesXBlock(runtime, element) { }); }); + //Reset Matching Game function resetMatching(initialState) { + //Function to reset the matching game to its 'start screen' state + $('.gamesxblock', element).empty(); expandGame(initialState); } - $(document).on('click', '.matching-replay-button', function(eventObject) { $.ajax({ type: "POST",