diff --git a/README.md b/README.md
index e240001b..5eb18edd 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,46 @@
-REPLACE THIS WITH A DESCRIPTION OF YOUR GAME (in the README.md file).
+# Game Name
+
+Boolean Bonanza
+
+# Team Color
+
+Teal
+
+# Developers
+
+- Angelo Ramos (angelora@udel.edu)
+- Patrick Tiamson (pattiam@udel.edu)
+
+# Blurb
+
+Dive into the exciting world of programming with our interactive puzzle game designed to make learning fun! Master the essential coding skill of boolean expressions as you solve engaging challenges that stimulate your mind. Perfect for beginners and experienced coders alike, our game transforms complex concepts into easy-to-understand, enjoyable puzzles. Whether you're looking to boost your programming skills or just love a good brain teaser, this game is your gateway to coding mastery. Ready to code and play your way to success? Join us and turn learning into an adventure!
+
+# Basic Instructions
+
+### Objective
+
+- Evaluate boolean expressions to `true` to complete objectives
+
+### Controls
+
+- **Arrow Keys (Up, down, left, right) or WASD Keys**: Swap and move tiles around the board
+- **R Key**: Select a row
+- **C Key**: Select a column
+- **ENTER Key**: Confirm your choice
+
+# Screenshot
+
+
+
+# Gameplay Video
+
+https://drive.google.com/file/d/1C1m25t_aci_TQ4Rk8dyEPvJn0G0vVa6e/view
+
+# Educational Game Design Document
+
+Link to our [EGDD](https://ud-s24-cisc374.github.io/final-project-teal/docs/egdd)
+
+# Credits
+
+- Children Drawing Vector Vectors by Vecteezy
+- Icon in hand draw style. Drawing with wax crayons, children's creativity Vectors by Vecteezy
diff --git a/assets/favicon.ico b/assets/favicon.ico
index f268a177..f5709f72 100644
Binary files a/assets/favicon.ico and b/assets/favicon.ico differ
diff --git a/assets/img/crumbled-paper-background.png b/assets/img/crumbled-paper-background.png
new file mode 100644
index 00000000..5cab952c
Binary files /dev/null and b/assets/img/crumbled-paper-background.png differ
diff --git a/assets/img/fail.png b/assets/img/fail.png
new file mode 100644
index 00000000..7980dddf
Binary files /dev/null and b/assets/img/fail.png differ
diff --git a/assets/img/game-over.png b/assets/img/game-over.png
new file mode 100644
index 00000000..3522d0db
Binary files /dev/null and b/assets/img/game-over.png differ
diff --git a/assets/img/level-complete.png b/assets/img/level-complete.png
new file mode 100644
index 00000000..9d163c42
Binary files /dev/null and b/assets/img/level-complete.png differ
diff --git a/assets/img/looseleaf.jpeg b/assets/img/looseleaf.jpeg
new file mode 100644
index 00000000..9ed34d1e
Binary files /dev/null and b/assets/img/looseleaf.jpeg differ
diff --git a/assets/img/soundSprites/sliderHandle.png b/assets/img/soundSprites/sliderHandle.png
new file mode 100644
index 00000000..824e1e33
Binary files /dev/null and b/assets/img/soundSprites/sliderHandle.png differ
diff --git a/assets/img/soundSprites/soundHigh.png b/assets/img/soundSprites/soundHigh.png
new file mode 100644
index 00000000..b8c9c3e2
Binary files /dev/null and b/assets/img/soundSprites/soundHigh.png differ
diff --git a/assets/img/soundSprites/soundLow.png b/assets/img/soundSprites/soundLow.png
new file mode 100644
index 00000000..928d5763
Binary files /dev/null and b/assets/img/soundSprites/soundLow.png differ
diff --git a/assets/img/soundSprites/soundMedium.png b/assets/img/soundSprites/soundMedium.png
new file mode 100644
index 00000000..0cc73756
Binary files /dev/null and b/assets/img/soundSprites/soundMedium.png differ
diff --git a/assets/img/soundSprites/soundMute.png b/assets/img/soundSprites/soundMute.png
new file mode 100644
index 00000000..2eff6473
Binary files /dev/null and b/assets/img/soundSprites/soundMute.png differ
diff --git a/assets/img/title-screen-boolean-bonanza.png b/assets/img/title-screen-boolean-bonanza.png
new file mode 100644
index 00000000..bc39e666
Binary files /dev/null and b/assets/img/title-screen-boolean-bonanza.png differ
diff --git a/assets/index.html b/assets/index.html
index c7ffa8cc..6083eedc 100644
--- a/assets/index.html
+++ b/assets/index.html
@@ -1,53 +1,89 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <%= htmlWebpackPlugin.options.gameName %>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= htmlWebpackPlugin.options.gameName %>
+
-
+
-
-
+
-
-
-
-
+
+
+
+
diff --git a/assets/music/bg-music-1.wav b/assets/music/bg-music-1.wav
new file mode 100644
index 00000000..240cbf09
Binary files /dev/null and b/assets/music/bg-music-1.wav differ
diff --git a/assets/sfx/click-1.wav b/assets/sfx/click-1.wav
new file mode 100644
index 00000000..19776b9d
Binary files /dev/null and b/assets/sfx/click-1.wav differ
diff --git a/assets/sfx/crumple-paper-1.wav b/assets/sfx/crumple-paper-1.wav
new file mode 100644
index 00000000..6d56e4be
Binary files /dev/null and b/assets/sfx/crumple-paper-1.wav differ
diff --git a/assets/sfx/incorrect-1.wav b/assets/sfx/incorrect-1.wav
new file mode 100644
index 00000000..08bbbd3d
Binary files /dev/null and b/assets/sfx/incorrect-1.wav differ
diff --git a/assets/sfx/pop-1.wav b/assets/sfx/pop-1.wav
new file mode 100644
index 00000000..9224d80d
Binary files /dev/null and b/assets/sfx/pop-1.wav differ
diff --git a/assets/sfx/pop-click-1.wav b/assets/sfx/pop-click-1.wav
new file mode 100644
index 00000000..1516b141
Binary files /dev/null and b/assets/sfx/pop-click-1.wav differ
diff --git a/assets/sfx/slide-1.wav b/assets/sfx/slide-1.wav
new file mode 100644
index 00000000..e1273a52
Binary files /dev/null and b/assets/sfx/slide-1.wav differ
diff --git a/assets/sfx/slide-2.wav b/assets/sfx/slide-2.wav
new file mode 100644
index 00000000..62d65275
Binary files /dev/null and b/assets/sfx/slide-2.wav differ
diff --git a/assets/sfx/sweep-1.wav b/assets/sfx/sweep-1.wav
new file mode 100644
index 00000000..b29042d3
Binary files /dev/null and b/assets/sfx/sweep-1.wav differ
diff --git a/assets/sketches/sketch-1.png b/assets/sketches/sketch-1.png
new file mode 100644
index 00000000..e7a65415
Binary files /dev/null and b/assets/sketches/sketch-1.png differ
diff --git a/assets/sketches/sketch-10.png b/assets/sketches/sketch-10.png
new file mode 100644
index 00000000..048e1819
Binary files /dev/null and b/assets/sketches/sketch-10.png differ
diff --git a/assets/sketches/sketch-11.png b/assets/sketches/sketch-11.png
new file mode 100644
index 00000000..37a4533a
Binary files /dev/null and b/assets/sketches/sketch-11.png differ
diff --git a/assets/sketches/sketch-12.png b/assets/sketches/sketch-12.png
new file mode 100644
index 00000000..e18520cc
Binary files /dev/null and b/assets/sketches/sketch-12.png differ
diff --git a/assets/sketches/sketch-2.png b/assets/sketches/sketch-2.png
new file mode 100644
index 00000000..7b303a98
Binary files /dev/null and b/assets/sketches/sketch-2.png differ
diff --git a/assets/sketches/sketch-3.png b/assets/sketches/sketch-3.png
new file mode 100644
index 00000000..6d30a9bd
Binary files /dev/null and b/assets/sketches/sketch-3.png differ
diff --git a/assets/sketches/sketch-4.png b/assets/sketches/sketch-4.png
new file mode 100644
index 00000000..f5884ee9
Binary files /dev/null and b/assets/sketches/sketch-4.png differ
diff --git a/assets/sketches/sketch-5.png b/assets/sketches/sketch-5.png
new file mode 100644
index 00000000..438a2bce
Binary files /dev/null and b/assets/sketches/sketch-5.png differ
diff --git a/assets/sketches/sketch-6.png b/assets/sketches/sketch-6.png
new file mode 100644
index 00000000..53b46964
Binary files /dev/null and b/assets/sketches/sketch-6.png differ
diff --git a/assets/sketches/sketch-7.png b/assets/sketches/sketch-7.png
new file mode 100644
index 00000000..137d7eed
Binary files /dev/null and b/assets/sketches/sketch-7.png differ
diff --git a/assets/sketches/sketch-8.png b/assets/sketches/sketch-8.png
new file mode 100644
index 00000000..746b8171
Binary files /dev/null and b/assets/sketches/sketch-8.png differ
diff --git a/assets/sketches/sketch-9.png b/assets/sketches/sketch-9.png
new file mode 100644
index 00000000..c6928b8c
Binary files /dev/null and b/assets/sketches/sketch-9.png differ
diff --git a/assets/story-img1.png b/assets/story-img1.png
new file mode 100644
index 00000000..43dfc1be
Binary files /dev/null and b/assets/story-img1.png differ
diff --git a/assets/story-img2.png b/assets/story-img2.png
new file mode 100644
index 00000000..b28cbe2b
Binary files /dev/null and b/assets/story-img2.png differ
diff --git a/assets/story-img3.png b/assets/story-img3.png
new file mode 100644
index 00000000..eda14603
Binary files /dev/null and b/assets/story-img3.png differ
diff --git a/assets/story-img4.png b/assets/story-img4.png
new file mode 100644
index 00000000..03afdb96
Binary files /dev/null and b/assets/story-img4.png differ
diff --git a/assets/story-img5.png b/assets/story-img5.png
new file mode 100644
index 00000000..7747abbc
Binary files /dev/null and b/assets/story-img5.png differ
diff --git a/assets/tiles/andTile.png b/assets/tiles/andTile.png
new file mode 100644
index 00000000..57412794
Binary files /dev/null and b/assets/tiles/andTile.png differ
diff --git a/assets/tiles/andTileSelect.png b/assets/tiles/andTileSelect.png
new file mode 100644
index 00000000..4dd32322
Binary files /dev/null and b/assets/tiles/andTileSelect.png differ
diff --git a/assets/tiles/falseTile.png b/assets/tiles/falseTile.png
new file mode 100644
index 00000000..f58b4c3d
Binary files /dev/null and b/assets/tiles/falseTile.png differ
diff --git a/assets/tiles/falseTileSelect.png b/assets/tiles/falseTileSelect.png
new file mode 100644
index 00000000..f73a850c
Binary files /dev/null and b/assets/tiles/falseTileSelect.png differ
diff --git a/assets/tiles/leftParenTile.png b/assets/tiles/leftParenTile.png
new file mode 100644
index 00000000..2128a79d
Binary files /dev/null and b/assets/tiles/leftParenTile.png differ
diff --git a/assets/tiles/leftParenTileSelect.png b/assets/tiles/leftParenTileSelect.png
new file mode 100644
index 00000000..2e26e634
Binary files /dev/null and b/assets/tiles/leftParenTileSelect.png differ
diff --git a/assets/tiles/notTile.png b/assets/tiles/notTile.png
new file mode 100644
index 00000000..775105ac
Binary files /dev/null and b/assets/tiles/notTile.png differ
diff --git a/assets/tiles/notTileSelect.png b/assets/tiles/notTileSelect.png
new file mode 100644
index 00000000..c51686b7
Binary files /dev/null and b/assets/tiles/notTileSelect.png differ
diff --git a/assets/tiles/orTile.png b/assets/tiles/orTile.png
new file mode 100644
index 00000000..90fadbc5
Binary files /dev/null and b/assets/tiles/orTile.png differ
diff --git a/assets/tiles/orTileSelect.png b/assets/tiles/orTileSelect.png
new file mode 100644
index 00000000..18c07d62
Binary files /dev/null and b/assets/tiles/orTileSelect.png differ
diff --git a/assets/tiles/rightParenTile.png b/assets/tiles/rightParenTile.png
new file mode 100644
index 00000000..e2648e57
Binary files /dev/null and b/assets/tiles/rightParenTile.png differ
diff --git a/assets/tiles/rightParenTileSelect.png b/assets/tiles/rightParenTileSelect.png
new file mode 100644
index 00000000..1b8e23b0
Binary files /dev/null and b/assets/tiles/rightParenTileSelect.png differ
diff --git a/assets/tiles/trueTile.png b/assets/tiles/trueTile.png
new file mode 100644
index 00000000..bad71093
Binary files /dev/null and b/assets/tiles/trueTile.png differ
diff --git a/assets/tiles/trueTileSelect.png b/assets/tiles/trueTileSelect.png
new file mode 100644
index 00000000..37cdd595
Binary files /dev/null and b/assets/tiles/trueTileSelect.png differ
diff --git a/assets/tiles/xorTile.png b/assets/tiles/xorTile.png
new file mode 100644
index 00000000..0c432442
Binary files /dev/null and b/assets/tiles/xorTile.png differ
diff --git a/assets/tiles/xorTileSelect.png b/assets/tiles/xorTileSelect.png
new file mode 100644
index 00000000..5c4c2c74
Binary files /dev/null and b/assets/tiles/xorTileSelect.png differ
diff --git a/assets/victorySketches/victorySketch1.png b/assets/victorySketches/victorySketch1.png
new file mode 100644
index 00000000..5dad9fd3
Binary files /dev/null and b/assets/victorySketches/victorySketch1.png differ
diff --git a/assets/victorySketches/victorySketch10.png b/assets/victorySketches/victorySketch10.png
new file mode 100644
index 00000000..53b1985a
Binary files /dev/null and b/assets/victorySketches/victorySketch10.png differ
diff --git a/assets/victorySketches/victorySketch11.png b/assets/victorySketches/victorySketch11.png
new file mode 100644
index 00000000..ee4728f2
Binary files /dev/null and b/assets/victorySketches/victorySketch11.png differ
diff --git a/assets/victorySketches/victorySketch12.png b/assets/victorySketches/victorySketch12.png
new file mode 100644
index 00000000..afeffb5c
Binary files /dev/null and b/assets/victorySketches/victorySketch12.png differ
diff --git a/assets/victorySketches/victorySketch13.png b/assets/victorySketches/victorySketch13.png
new file mode 100644
index 00000000..7be65387
Binary files /dev/null and b/assets/victorySketches/victorySketch13.png differ
diff --git a/assets/victorySketches/victorySketch14.png b/assets/victorySketches/victorySketch14.png
new file mode 100644
index 00000000..a2672e3f
Binary files /dev/null and b/assets/victorySketches/victorySketch14.png differ
diff --git a/assets/victorySketches/victorySketch15.png b/assets/victorySketches/victorySketch15.png
new file mode 100644
index 00000000..188576be
Binary files /dev/null and b/assets/victorySketches/victorySketch15.png differ
diff --git a/assets/victorySketches/victorySketch16.png b/assets/victorySketches/victorySketch16.png
new file mode 100644
index 00000000..e5fc436b
Binary files /dev/null and b/assets/victorySketches/victorySketch16.png differ
diff --git a/assets/victorySketches/victorySketch17.png b/assets/victorySketches/victorySketch17.png
new file mode 100644
index 00000000..c577e6b7
Binary files /dev/null and b/assets/victorySketches/victorySketch17.png differ
diff --git a/assets/victorySketches/victorySketch18.png b/assets/victorySketches/victorySketch18.png
new file mode 100644
index 00000000..f69c3701
Binary files /dev/null and b/assets/victorySketches/victorySketch18.png differ
diff --git a/assets/victorySketches/victorySketch19.png b/assets/victorySketches/victorySketch19.png
new file mode 100644
index 00000000..6803dd80
Binary files /dev/null and b/assets/victorySketches/victorySketch19.png differ
diff --git a/assets/victorySketches/victorySketch2.png b/assets/victorySketches/victorySketch2.png
new file mode 100644
index 00000000..121a16c6
Binary files /dev/null and b/assets/victorySketches/victorySketch2.png differ
diff --git a/assets/victorySketches/victorySketch20.png b/assets/victorySketches/victorySketch20.png
new file mode 100644
index 00000000..afd74365
Binary files /dev/null and b/assets/victorySketches/victorySketch20.png differ
diff --git a/assets/victorySketches/victorySketch21.png b/assets/victorySketches/victorySketch21.png
new file mode 100644
index 00000000..03221598
Binary files /dev/null and b/assets/victorySketches/victorySketch21.png differ
diff --git a/assets/victorySketches/victorySketch22.png b/assets/victorySketches/victorySketch22.png
new file mode 100644
index 00000000..fabebf19
Binary files /dev/null and b/assets/victorySketches/victorySketch22.png differ
diff --git a/assets/victorySketches/victorySketch23.png b/assets/victorySketches/victorySketch23.png
new file mode 100644
index 00000000..4e1c37b2
Binary files /dev/null and b/assets/victorySketches/victorySketch23.png differ
diff --git a/assets/victorySketches/victorySketch24.png b/assets/victorySketches/victorySketch24.png
new file mode 100644
index 00000000..3c66cc18
Binary files /dev/null and b/assets/victorySketches/victorySketch24.png differ
diff --git a/assets/victorySketches/victorySketch25.png b/assets/victorySketches/victorySketch25.png
new file mode 100644
index 00000000..9563061c
Binary files /dev/null and b/assets/victorySketches/victorySketch25.png differ
diff --git a/assets/victorySketches/victorySketch26.png b/assets/victorySketches/victorySketch26.png
new file mode 100644
index 00000000..d0ad7af8
Binary files /dev/null and b/assets/victorySketches/victorySketch26.png differ
diff --git a/assets/victorySketches/victorySketch3.png b/assets/victorySketches/victorySketch3.png
new file mode 100644
index 00000000..2201f9c9
Binary files /dev/null and b/assets/victorySketches/victorySketch3.png differ
diff --git a/assets/victorySketches/victorySketch4.png b/assets/victorySketches/victorySketch4.png
new file mode 100644
index 00000000..9685ff66
Binary files /dev/null and b/assets/victorySketches/victorySketch4.png differ
diff --git a/assets/victorySketches/victorySketch5.png b/assets/victorySketches/victorySketch5.png
new file mode 100644
index 00000000..4699bc48
Binary files /dev/null and b/assets/victorySketches/victorySketch5.png differ
diff --git a/assets/victorySketches/victorySketch6.png b/assets/victorySketches/victorySketch6.png
new file mode 100644
index 00000000..50e80a34
Binary files /dev/null and b/assets/victorySketches/victorySketch6.png differ
diff --git a/assets/victorySketches/victorySketch7.png b/assets/victorySketches/victorySketch7.png
new file mode 100644
index 00000000..3752b370
Binary files /dev/null and b/assets/victorySketches/victorySketch7.png differ
diff --git a/assets/victorySketches/victorySketch8.png b/assets/victorySketches/victorySketch8.png
new file mode 100644
index 00000000..85069316
Binary files /dev/null and b/assets/victorySketches/victorySketch8.png differ
diff --git a/assets/victorySketches/victorySketch9.png b/assets/victorySketches/victorySketch9.png
new file mode 100644
index 00000000..da13f447
Binary files /dev/null and b/assets/victorySketches/victorySketch9.png differ
diff --git a/docs/egdd.md b/docs/egdd.md
index 51ed6536..374a7985 100644
--- a/docs/egdd.md
+++ b/docs/egdd.md
@@ -1 +1,190 @@
-REPLACE THIS TEXT WITH YOUR EGDD MARKDOWN.
+# Boolean Bonanza
+
+## Elevator Pitch
+
+Picture a game where players dive into the exciting world of programming through interactive puzzles, focusing specifically on mastering boolean expressions, empowering learners with essential coding skills in a fun and engaging way!
+
+## Influences (Brief)
+
+- Candy Crush:
+ - Medium: Mobile Game
+ - Explanation: Candy crush is a simple match-3 puzzle game where you swap candies to match three or more of the same color. The game has similiar gameplay as candy crush.
+- Yoshi's Cookie:
+ - Medium: Game
+ - Explanation: The game will have similar gameplay to Yoshi's cookie where you have to match rows
+
+## Core Gameplay Mechanics (Brief)
+
+- Click on tiles to swap between two tiles
+- Having a row or column that has a true boolean will give you points and also delete the tiles
+- Each level has a goal that you have to reach to advance to the next stage
+- Each level gets more difficult with new boolean logic
+- If a player doesn't complete the level in a certain amount of time then they fail and have to retry
+
+# Learning Aspects
+
+## Learning Domains
+
+Introduction to boolean expressions
+
+## Target Audiences
+
+* Students studying computer science or programming at a beginner level
+* Anyone seeking to understand basic programming concepts
+
+## Target Contexts
+
+* This would be used in introductory computer science classes teaching boolean expressions
+
+## Learning Objectives
+
+*Remember, Learning Objectives are NOT simply topics. They are statements of observable behavior that a learner can do after the learning experience. You cannot observe someone "understanding" or "knowing" something.*
+
+- By the end of the lesson, players will be able to identify true or false boolean expressions
+- By the end of the lesson, players will be able to apply boolean logic to solve simple programming problems
+- By the end of the lesson, players will be able to construct boolean expressions involving logical operators AND, OR, NOT, and XOR
+
+## Prerequisite Knowledge
+
+*What do they need to know prior to trying this game?*
+
+- Players should be familiar with boolean data type
+- Players should know simple logic operators (AND, OR, NOT)
+- Players should have a basic understanding of boolean algebra
+
+## Assessment Measures
+
+A short pre-test and matching post-test should be designed to assess student learning
+
+- Given a boolean expression, identify if the expression evaluates to true or false
+- Construct a boolean expression using certain logical operators
+
+# What sets this project apart?
+
+- Unlike most games, this one is designed to specifically teach fundamental programming concepts
+- Players interact with boolean expressions through gameplay, reinforcing their understanding
+
+# Player Interaction Patterns and Modes
+
+## Player Interaction Pattern
+
+This is a one person game. The player clicks on two tiles to swap them
+
+## Player Modes
+
+- Single-player: You repeatedly advance through levels until you reach the end
+
+# Gameplay Objectives
+
+- Create true boolean expressions:
+ - Description: Complete level-specific goals by creating rows or columns of true boolean expressions. Score more points by getting more rows/columns.
+ - Alignment: Reinforces understanding of boolean expressions and operators.
+- Unlock new and more challenging levels:
+ - Description: Progress through increasingly challenging levels, introducing new boolean logic.
+ - Alignment: Provides opportunities to apply and expand knowledge of boolean concepts.
+
+
+# Procedures/Actions
+
+- Players swap tiles to create rows or columns of true boolean expressions.
+- Each level presents specific goals to achieve within a time limit.
+
+# Rules
+
+- Players can swap adjacent tiles to create matches.
+- Complete a level by getting the desired score within specified constraints like time limits or move/swap limits
+- Different tiles represent true, false, and logical operators
+
+# Objects/Entities
+- Tiles
+- Animations/Particles
+- Music/SFX
+- Navigation Menus
+
+## Core Gameplay Mechanics (Detailed)
+
+- Swap Tiles: Players swap tiles to create rows or columns of true boolean expressions, with different tiles representing true, false, and logical operators. An animation will occur on swipe and effects will pop up when the row/column is complete.
+- Level Up: Each level presents specific goals to achieve using boolean expressions, introducing new logic and challenges. Each level is randomly generated but offers new challenges such as different boolean operators. There will be more rows and columns as the player progresses
+- Time Limit: Time limit adds urgency, encouraging quick thinking and decision-making.
+
+
+## Feedback
+
+Short Term Feedback:
+- "Ding" noises on correct swipes
+- Score Indicator (text) showcase how well they are doing in the game
+
+Long Term Feedback:
+- Leveling System Will Indicate Long Term Progress
+- Visuals that indicate What Level You are (Progress Bar)
+
+# Story and Gameplay
+
+## Presentation of Rules
+
+- An animamtion of a cursor swapping two tiles to show how the swap tiles
+- Tutorial that shows how to make completed row/col on a small board (3x3)
+- Highlight the on-screen timer during the tutorial mode
+- Visual and audio cues indicate successful matches and progress towards level goals.
+
+## Presentation of Content
+
+- Introduce a small interactice tutorial when a new logical operator is added to the level.
+- Possible tutorial menu to relearn boolean concepts
+
+## Story (Brief)
+
+Boolean Bonanza challenges players with progressively difficult puzzles, immersing them in the world of boolean expressions. The graphics are fun similar to Candy Crush.
+
+## Storyboarding
+
+[](https://docs.google.com/presentation/d/1ByYG8yYKBdqLnavDl0nlWLJHGGYMCE1L/edit?usp=sharing&ouid=112997788902853184335&rtpof=true&sd=true)
+
+[](https://docs.google.com/presentation/d/1ByYG8yYKBdqLnavDl0nlWLJHGGYMCE1L/edit?usp=sharing&ouid=112997788902853184335&rtpof=true&sd=true)
+
+[](https://docs.google.com/presentation/d/1ByYG8yYKBdqLnavDl0nlWLJHGGYMCE1L/edit?usp=sharing&ouid=112997788902853184335&rtpof=true&sd=true)
+
+[](https://docs.google.com/presentation/d/1ByYG8yYKBdqLnavDl0nlWLJHGGYMCE1L/edit?usp=sharing&ouid=112997788902853184335&rtpof=true&sd=true)
+
+[](https://docs.google.com/presentation/d/1ByYG8yYKBdqLnavDl0nlWLJHGGYMCE1L/edit?usp=sharing&ouid=112997788902853184335&rtpof=true&sd=true)
+
+# Assets Needed
+
+## Aethestics
+
+- Vibrant visuals with a playful, educational atmosphere.
+- Engaging sound effects and music enhancing the gaming experience.
+
+## Graphical
+
+- Characters List
+ - No "characters"
+ - Tiles representing true, false, and logical operators
+- Textures:
+ - Playful cube textures for the tile
+ - Fun colors similar to Candy Crush
+- Environment Art/Textures:
+ - Menu graphics
+ - Level Map graphics
+ - Background art during levels
+
+
+## Audio
+
+- Music List (Ambient sound)
+ - In Game Music: Ambient music to accompany gameplay, increases pace as time runs out
+ - Main menu music: Fun, 8-bit style music when player is in main menu/choosing a level
+
+*Game Interactions are things that trigger SFX, like character movement, hitting a spiky enemy, collecting a coin.*
+
+- Sound List (SFX)
+ - Swapping Tiles: the sound of tiles rubbing against each other
+ - Scoring Points: coin collection sound
+ - Completed Rows/Column: Mini Checkmark noise (like the duolingo sound)
+ - Clicking/Hovering over Menu Items: Short click noises for auditory feedback
+
+
+# Metadata
+
+* Template created by Austin Cory Bart , Mark Sheriff, Alec Markarian, and Benjamin Stanley.
+* Version 0.0.3
\ No newline at end of file
diff --git a/docs/large.png b/docs/large.png
new file mode 100644
index 00000000..8d86bc80
Binary files /dev/null and b/docs/large.png differ
diff --git a/docs/small.png b/docs/small.png
new file mode 100644
index 00000000..e0de145c
Binary files /dev/null and b/docs/small.png differ
diff --git a/package-lock.json b/package-lock.json
index 279e7740..4b4d31a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,11 +1,11 @@
{
- "name": "coding-3-phaser-scenes",
+ "name": "final-project",
"version": "4.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "coding-3-phaser-scenes",
+ "name": "final-project",
"version": "4.0.0",
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index e285b2ae..cc53c425 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
{
- "name": "final-project",
+ "name": "boolean-bonanza",
"version": "4.0.0",
- "description": "Phaser 3 Final Project",
- "homepage": "https://github.com/UD-S24-CISC374/final-project-template#readme",
+ "description": "Boolean Bonanza Game",
+ "homepage": "https://github.com/UD-S24-CISC374/final-project-teal#readme",
"main": "index.js",
"scripts": {
"start": "webpack serve --config webpack/webpack.dev.js",
diff --git a/pwa/icons/icons-192.png b/pwa/icons/icons-192.png
index 16bebab8..ecced14c 100644
Binary files a/pwa/icons/icons-192.png and b/pwa/icons/icons-192.png differ
diff --git a/pwa/icons/icons-512.png b/pwa/icons/icons-512.png
index 0e536097..9575d388 100644
Binary files a/pwa/icons/icons-512.png and b/pwa/icons/icons-512.png differ
diff --git a/pwa/manifest.json b/pwa/manifest.json
index fa202a58..3b39b14b 100644
--- a/pwa/manifest.json
+++ b/pwa/manifest.json
@@ -1,6 +1,6 @@
{
- "short_name": "Phaser Game",
- "name": "My Cool Phaser 3 Game",
+ "short_name": "Boolean Bonanza",
+ "name": "Boolean Bonanza",
"icons": [
{
"src": "./icons/icons-192.png",
diff --git a/src/.DS_Store b/src/.DS_Store
new file mode 100644
index 00000000..d28e1af3
Binary files /dev/null and b/src/.DS_Store differ
diff --git a/src/config.ts b/src/config.ts
index 9776bc5c..cf4fbec8 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,12 +1,21 @@
import Phaser from "phaser";
import MainScene from "./scenes/mainScene";
import PreloadScene from "./scenes/preloadScene";
+import MenuScene from "./scenes/menuScene";
+import ControlScene from "./scenes/controlScene";
+import ProgressionScene from "./scenes/progressionScene";
+import CreditsScene from "./scenes/creditsScene";
+import GameOverScene from "./scenes/gameOverScene";
+import TutorialScene from "./scenes/tutorialScene";
+import GameVictoryScene from "./scenes/gameVictoryScene";
+import SettingsScene from "./scenes/settingsScene";
+import FreeplayScene from "./scenes/freeplayScene";
const DEFAULT_WIDTH = 1280;
const DEFAULT_HEIGHT = 720;
export const CONFIG = {
- title: "My Untitled Phaser 3 Game",
+ title: "Boolean Bonanza!",
version: "0.0.1",
type: Phaser.AUTO,
backgroundColor: "#ffffff",
@@ -17,7 +26,19 @@ export const CONFIG = {
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
},
- scene: [PreloadScene, MainScene],
+ scene: [
+ PreloadScene,
+ MainScene,
+ MenuScene,
+ ControlScene,
+ ProgressionScene,
+ CreditsScene,
+ GameOverScene,
+ TutorialScene,
+ GameVictoryScene,
+ SettingsScene,
+ FreeplayScene,
+ ],
physics: {
default: "arcade",
arcade: {
diff --git a/src/objects/Background.ts b/src/objects/Background.ts
new file mode 100644
index 00000000..cf9e83ae
--- /dev/null
+++ b/src/objects/Background.ts
@@ -0,0 +1,34 @@
+import Phaser from "phaser";
+
+export default class Background {
+ private static backgroundInstance: Background;
+ private scene: Phaser.Scene;
+ private imageKey: string;
+ private backgroundImage: Phaser.GameObjects.Image;
+
+ private constructor(scene: Phaser.Scene, imageKey: string) {
+ this.scene = scene;
+ this.imageKey = imageKey;
+ }
+
+ create() {
+ this.backgroundImage = this.scene.add.image(0, 0, this.imageKey);
+ this.backgroundImage.setOrigin(0, 0);
+
+ const scaleX =
+ (this.scene.game.config.width as number) /
+ this.backgroundImage.width;
+ const scaleY =
+ (this.scene.game.config.height as number) /
+ this.backgroundImage.height;
+ const maxScale = Math.max(scaleX, scaleY);
+ this.backgroundImage.setScale(maxScale);
+ }
+
+ public static getInstance(
+ scene: Phaser.Scene,
+ imageKey: string
+ ): Background {
+ return new Background(scene, imageKey);
+ }
+}
diff --git a/src/objects/Board.ts b/src/objects/Board.ts
new file mode 100644
index 00000000..c6b37b5e
--- /dev/null
+++ b/src/objects/Board.ts
@@ -0,0 +1,551 @@
+import Phaser from "phaser";
+import Tile from "./Tile";
+import SFX from "./SFX";
+
+enum DirectionType {
+ ROW,
+ COL,
+ NONE,
+}
+
+export default class Board {
+ private sfx: SFX;
+
+ private scene: Phaser.Scene;
+ private tiles: Tile[][];
+ private boardSize: number;
+ private tileSize: number;
+ private tileTypes: string[];
+
+ private selectedTile: Tile | null = null;
+ private selectedTiles: Tile[] = [];
+ public lastRemovedTileTypes: string[] = [];
+
+ private currentDirection: DirectionType = DirectionType.NONE;
+
+ private isAnimating: boolean = false;
+ private name: string;
+
+ constructor(
+ scene: Phaser.Scene,
+ boardSize: number,
+ tileTypes: string[],
+ name: string
+ ) {
+ this.scene = scene;
+ this.boardSize = boardSize;
+ this.tileTypes = tileTypes;
+ this.name = name;
+ this.sfx = SFX.getInstance(scene);
+ this.tiles = this.generateBoard();
+ }
+
+ private generateBoard(): Tile[][] {
+ console.log("game name: " + this.name);
+ if (this.name === "Tutorial") {
+ return this.generateLvlOneBoard();
+ }
+ const board: Tile[][] = [];
+
+ const screenWidth = this.scene.game.scale.width;
+ const screenHeight = this.scene.game.scale.height;
+ //const boardWidth = this.boardSize * this.tileSize;
+ //const boardHeight = this.boardSize * this.tileSize;
+ const boardHeight = screenHeight * 0.8;
+ const boardWidth = boardHeight;
+ const boardX = (screenWidth - boardWidth) / 2;
+ const boardY = (screenHeight - boardHeight) / 2;
+ this.tileSize = boardHeight / this.boardSize;
+
+ for (let row = 0; row < this.boardSize; row++) {
+ board[row] = [];
+ for (let col = 0; col < this.boardSize; col++) {
+ const tileX = boardX + col * this.tileSize;
+ const tileY = boardY + row * this.tileSize;
+ const tileTypeKey = Phaser.Math.RND.pick(this.tileTypes);
+ const tile = new Tile(
+ this.scene,
+ tileX,
+ tileY,
+ tileTypeKey,
+ this.tileSize
+ );
+ tile.setInteractive();
+ tile.on("pointerdown", this.selectTile.bind(this, tile));
+ tile.setOrigin(0, 0);
+ tile.setScale(this.tileSize / tile.width);
+ board[row][col] = tile;
+ }
+ }
+
+ return board;
+ }
+
+ public regenerateBoard() {
+ for (let i = 0; i < this.boardSize; i++) {
+ for (let j = 0; j < this.boardSize; j++) {
+ this.tiles[i][j].destroy();
+ this.tiles[i][j].hideOverlay();
+ }
+ }
+ this.tiles = this.generateBoard();
+ }
+
+ private selectTile(tile: Tile) {
+ this.sfx.play("click-1");
+ this.unselectTiles();
+ if (this.selectedTile) {
+ this.selectedTile.hideOverlay();
+ }
+ this.selectedTile = tile;
+ tile.showOverlay();
+ }
+
+ // callback : function to call after the swap is done
+ public swapTiles(
+ row1: number,
+ col1: number,
+ row2: number,
+ col2: number,
+ callback: () => void
+ ) {
+ if (this.isAnimating) {
+ return;
+ }
+ this.sfx.play("slide-1");
+ this.isAnimating = true;
+ this.unselectTiles();
+ const tile1 = this.tiles[row1][col1];
+ const tile2 = this.tiles[row2][col2];
+ const tweenDuration = 300;
+
+ this.tiles[row2][col2] = tile1;
+ this.tiles[row1][col1] = tile2;
+
+ this.selectTile(tile1);
+
+ //create tile1 tween
+ this.scene.tweens.add({
+ targets: [tile1, tile1.overlay],
+ x: tile2.x,
+ y: tile2.y,
+ duration: tweenDuration,
+ onComplete: () => {
+ this.isAnimating = false;
+
+ //this.selectedTile?.setTint(0xffffff);
+ //this.selectedTile = null; // Deselect the tile after swapping
+ },
+ });
+ //create tile2 tween
+ this.scene.tweens.add({
+ targets: [tile2, tile2.overlay],
+ x: tile1.x,
+ y: tile1.y,
+ duration: tweenDuration,
+ });
+ callback();
+ }
+
+ public selectTiles(directionIndex: number, direction: DirectionType) {
+ this.sfx.play("sweep-1");
+ this.unselectTiles();
+ this.currentDirection = direction;
+ if (direction == DirectionType.ROW) {
+ for (let tileIndex = 0; tileIndex < this.boardSize; tileIndex++) {
+ const tile = this.tiles[directionIndex][tileIndex];
+ this.selectedTiles.push(tile);
+ tile.showOverlay();
+ }
+ } else if (direction == DirectionType.COL) {
+ for (let tileIndex = 0; tileIndex < this.boardSize; tileIndex++) {
+ const tile = this.tiles[tileIndex][directionIndex];
+ this.selectedTiles.push(tile);
+ tile.showOverlay();
+ }
+ }
+ }
+ private unselectTiles() {
+ while (this.selectedTiles.length > 0) {
+ const tile = this.selectedTiles.shift();
+ tile?.hideOverlay();
+ }
+ this.currentDirection = DirectionType.NONE;
+ }
+
+ private checkRow(currentRow: number): boolean {
+ let row = this.tileToString(this.tiles[currentRow]);
+ try {
+ if (eval(row)) {
+ this.removeTilesRow(currentRow);
+ this.sfx.play("pop-1");
+ return true;
+ }
+ } catch (e) {
+ console.log(e);
+ }
+
+ this.shakeTiles();
+ return false;
+ }
+
+ // convert the tiles to string format ex: [true, and, true] => "true && true"
+ private tileToString(tiles: Tile[]): string {
+ return tiles
+ .map((tile) => {
+ switch (tile.tileType) {
+ case "trueTile":
+ return "true";
+ case "falseTile":
+ return "false";
+ case "andTile":
+ return " && ";
+ case "orTile":
+ return " || ";
+ case "leftParenTile":
+ return "(";
+ case "rightParenTile":
+ return ")";
+ case "xorTile":
+ return " ^ ";
+ case "notTile":
+ return "!";
+ default:
+ return "Invalid tile type";
+ }
+ })
+ .join("");
+ }
+
+ private removeTilesRow(currentRow: number) {
+ this.lastRemovedTileTypes = [];
+ // remove all the tiles in the current row
+ for (let col = 0; col < this.boardSize; col++) {
+ const tile = this.tiles[currentRow][col];
+ this.lastRemovedTileTypes.push(tile.tileType);
+ tile.destroy();
+ tile.hideOverlay();
+ }
+
+ // move all the tiles above the current row down
+ for (let row = currentRow; row > 0; row--) {
+ for (let col = 0; col < this.boardSize; col++) {
+ const tile = this.tiles[row - 1][col];
+ this.tiles[row][col] = tile;
+ this.scene.tweens.add({
+ targets: [tile, tile.overlay],
+ y: tile.y + this.tileSize,
+ duration: 300,
+ });
+ }
+ }
+ this.addTilesRow();
+ }
+
+ private addTilesRow() {
+ for (let col = 0; col < this.boardSize; col++) {
+ const tileX = this.tiles[0][col].x;
+ const tileY = this.tiles[0][col].y;
+ const tileTypeKey = Phaser.Math.RND.pick(this.tileTypes);
+ const tile = new Tile(
+ this.scene,
+ tileX,
+ tileY - this.tileSize,
+ tileTypeKey,
+ this.tileSize
+ );
+
+ tile.setInteractive();
+ tile.on("pointerdown", this.selectTile.bind(this, tile));
+ tile.setOrigin(0, 0);
+ tile.setScale(this.tileSize / tile.width);
+ this.tiles[0][col] = tile;
+
+ this.scene.tweens.add({
+ targets: [tile, tile.overlay],
+ y: tile.y + this.tileSize,
+ duration: 300,
+ });
+ }
+ }
+
+ private checkCol(currentCol: number): boolean {
+ let column = this.tiles.map((row) => row[currentCol]);
+ let col = this.tileToString(column);
+ try {
+ if (eval(col)) {
+ this.removeTilesCol(currentCol);
+ this.sfx.play("pop-1");
+ return true;
+ }
+ } catch (e) {
+ console.log(e);
+ }
+
+ this.shakeTiles();
+ return false;
+ }
+
+ private removeTilesCol(currentCol: number) {
+ this.lastRemovedTileTypes = [];
+ for (let row = 0; row < this.boardSize; row++) {
+ const tile = this.tiles[row][currentCol];
+ this.lastRemovedTileTypes.push(tile.tileType);
+ tile.destroy();
+ tile.hideOverlay();
+ }
+ //this.addScore(10);
+ this.addTilesCol(currentCol);
+ }
+
+ private addTilesCol(currentCol: number) {
+ for (let row = 0; row < this.boardSize; row++) {
+ const tileX = this.tiles[row][currentCol].x;
+ const tileY = this.tiles[row][0].y;
+ const tileTypeKey = Phaser.Math.RND.pick(this.tileTypes);
+ const tile = new Tile(
+ this.scene,
+ tileX,
+ tileY - this.boardSize * this.tileSize,
+ tileTypeKey,
+ this.tileSize
+ );
+ tile.setInteractive();
+ tile.on("pointerdown", this.selectTile.bind(this, tile));
+ tile.setOrigin(0, 0);
+ tile.setScale(this.tileSize / tile.width);
+ this.tiles[row][currentCol] = tile;
+
+ this.scene.tweens.add({
+ targets: [tile, tile.overlay],
+ y: tile.y + this.tileSize * this.boardSize,
+ duration: 450,
+ });
+ }
+ }
+
+ private shakeTiles() {
+ if (this.isAnimating) {
+ return;
+ }
+ this.isAnimating = true;
+ this.sfx.play("incorrect-1");
+ this.selectedTiles.forEach((tile) => {
+ const originalX = tile.x;
+ const originalY = tile.y;
+
+ this.scene.tweens.add({
+ targets: [tile, tile.overlay],
+ duration: 100, // Duration of each shake segment
+ repeat: 2, // Number of shakes
+ yoyo: true, // Go back and forth
+ ease: "Sine.easeInOut", // Easing function to make it smooth
+ x: {
+ getStart: () => originalX - 5, // Start 5 pixels to the left
+ getEnd: () => originalX + 5, // End 5 pixels to the right
+ },
+ onComplete: () => {
+ // Optional: Reset sprite position after shaking, in case of rounding errors
+ tile.x = originalX;
+ tile.y = originalY;
+ tile.overlay.x = originalX;
+ tile.overlay.y = originalY;
+ this.isAnimating = false;
+ },
+ });
+ });
+ }
+
+ public isTileSelected(): boolean {
+ return this.selectedTile !== null;
+ }
+
+ public findSelectedTile(): number[] {
+ if (!this.selectedTile) {
+ return [-1, 1];
+ }
+
+ let currentRow = -1;
+ let currentCol = -1;
+
+ // Find the row and column of the selected tile
+ for (let row = 0; row < this.boardSize; row++) {
+ const col = this.tiles[row].indexOf(this.selectedTile);
+ if (col !== -1) {
+ currentCol = col;
+ currentRow = row;
+ break;
+ }
+ }
+
+ return [currentRow, currentCol];
+ }
+
+ public getBoardSize(): number {
+ return this.boardSize;
+ }
+
+ public handleRowColCheck(
+ currentRow: number,
+ currentCol: number,
+ callback: () => void
+ ): boolean {
+ if (this.currentDirection == DirectionType.ROW) {
+ callback();
+ return this.checkRow(currentRow);
+ } else if (this.currentDirection == DirectionType.COL) {
+ callback();
+ return this.checkCol(currentCol);
+ }
+ this.currentDirection = DirectionType.NONE;
+ return false;
+ }
+
+ public generateLvlOneBoard(): Tile[][] {
+ const tileKeys = [
+ ["trueTile", "orTile", "falseTile"],
+ ["trueTile", "orTile", "falseTile"],
+ ["trueTile", "orTile", "falseTile"],
+ ];
+
+ const screenHeight = this.scene.game.scale.height;
+ const screenWidth = this.scene.game.scale.width;
+ const boardHeight = screenHeight * 0.8;
+ const boardWidth = boardHeight;
+ const boardX = (screenWidth - boardWidth) / 2;
+ const boardY = (screenHeight - boardHeight) / 2;
+ this.tileSize = boardHeight / this.boardSize;
+
+ const board: Tile[][] = [];
+ for (let row = 0; row < this.boardSize; row++) {
+ board[row] = [];
+ for (let col = 0; col < this.boardSize; col++) {
+ const tileX = boardX + col * this.tileSize;
+ const tileY = boardY + row * this.tileSize;
+ const tileTypeKey = tileKeys[row][col];
+ const tile = new Tile(
+ this.scene,
+ tileX,
+ tileY,
+ tileTypeKey,
+ this.tileSize
+ );
+ tile.setInteractive();
+ tile.on("pointerdown", this.selectTile.bind(this, tile));
+ tile.setOrigin(0, 0);
+ tile.setScale(this.tileSize / tile.width);
+ board[row][col] = tile;
+ }
+ }
+ return board;
+ }
+ public isPossibleSolution(): boolean {
+ let size = this.boardSize;
+ let tileTypeCount: { [key: string]: number } = {};
+
+ for (let i = 0; i < size; i++) {
+ for (let j = 0; j < size; j++) {
+ let tileType = this.tiles[i][j].tileType;
+ if (tileType) {
+ if (tileTypeCount[tileType]) {
+ tileTypeCount[tileType]++;
+ } else {
+ tileTypeCount[tileType] = 1;
+ }
+ }
+ }
+ }
+
+ const getCount = (key: string): number => tileTypeCount[key] || 0;
+
+ //pattern for calculating "usable NOTs"
+ //tileTypeCount["notTile"] = Math.floor(size / 3) + (size % 3) - 1;
+ //POSSIBLE EDGE CASE: IF THERE ARE PARENTHESES, THEN THAT WOULD ADJUST THE POTENTIAL "SIZE"
+ //I THINK THE ONLY FIX IS TO HAVE DIFFERENT USABLE NOTS DEPENDING ON PARENTHESES
+
+ const sizeReduction =
+ 2 *
+ Math.min(
+ getCount("leftParenTile"),
+ getCount("rightParenTile")
+ ) +
+ getCount("notTile");
+ let maxReduction = false;
+ if (sizeReduction >= size - 1) {
+ maxReduction = true;
+ }
+ size -= sizeReduction;
+
+ size -= 1 - (size % 2);
+
+ //need to account for NOTs?
+ size = Math.max(size, 3);
+
+ const operatorCount =
+ getCount("orTile") + getCount("andTile") + getCount("xorTile");
+ const literalCount = getCount("falseTile") + getCount("trueTile");
+ const requiredOperatorCount = (size - 1) / 2;
+ const requiredLiteralCount = (size + 1) / 2;
+
+ //adjusts literal count based off of usable Nots
+ const temp = getCount("falseTile");
+ tileTypeCount["falseTile"] =
+ getCount("falseTile") +
+ Math.min(getCount("notTile"), getCount("trueTile"));
+ tileTypeCount["trueTile"] =
+ getCount("trueTile") + Math.min(getCount("notTile"), temp);
+ console.log(tileTypeCount);
+
+ //Solution 1: 1 or and 1 true, and enough literals and operators can always make a true
+ const solution1 = getCount("orTile") >= 1 && getCount("trueTile") >= 1;
+ //solution 2: if no ors, then solution is possible with enough ands and enough trues
+ const solution2 =
+ getCount("andTile") >= requiredOperatorCount &&
+ getCount("trueTile") >= requiredLiteralCount;
+
+ //solution 3: with enough parentheses, only the true tile is needed
+ const solution3 = getCount("trueTile") >= 1 && maxReduction;
+
+ //solution 4: with 1 xor, you just need one of each literal (problem 3 already checks for at least 1 true tile)
+ const solution4 =
+ getCount("xorTile") == 1 && getCount("falseTile") >= 1;
+
+ //solution5: with 2 or more xor, you just need enough operators (no false tile is needed)
+ const solution5 = getCount("xorTile") >= 2;
+
+ //problem1: with an even board size, a NOT is needed to make a valid expression
+ const problem1 = size % 2 == 0 && getCount("notTile") == 0;
+ //problem2: you need a certain number of operators and literals to make a valid expression
+ //but if you have enough parentheses/NOTs then you only need 1 operator for even boards and 0 operators for odd boards
+ const problem2 =
+ (operatorCount < requiredOperatorCount ||
+ literalCount < requiredLiteralCount) &&
+ !solution3 &&
+ operatorCount > 1 - (this.boardSize % 1);
+ //problem3: you always need a TRUE to get an expression that evaluates to true
+ const problem3 = getCount("trueTile") == 0;
+
+ //problem4: if you only have xor tiles, you need an additional literal tile to create an exprssion that evaluates to true
+ const problem4 =
+ getCount("xorTiles") == operatorCount &&
+ literalCount < requiredLiteralCount + 1;
+ const anyValidSolution =
+ (solution1 || solution2 || solution3 || solution4 || solution5) &&
+ !(problem1 || problem2 || problem3 || problem4);
+ console.log(`Any valid solution: ${anyValidSolution}`);
+ console.log(
+ `Solution chain: ${[
+ solution1,
+ solution2,
+ solution3,
+ solution4,
+ solution5,
+ ]}`
+ );
+ console.log(
+ `Problem chain: ${[problem1, problem2, problem3, problem4]}`
+ );
+
+ return anyValidSolution;
+ }
+}
diff --git a/src/objects/Button.ts b/src/objects/Button.ts
new file mode 100644
index 00000000..f1b12cf5
--- /dev/null
+++ b/src/objects/Button.ts
@@ -0,0 +1,56 @@
+import Phaser from "phaser";
+import SFX from "./SFX";
+
+export default class Button extends Phaser.GameObjects.Text {
+ private defaultBackgroundColor: string;
+ private hoverBackgroundColor: string;
+ private hoverScale: number;
+ private sfx: SFX;
+
+ constructor(
+ scene: Phaser.Scene,
+ x: number,
+ y: number,
+ text: string,
+ callback: () => void,
+ fontSize = "32px",
+ fontFamily = "Arial",
+ color = "#ffffff",
+ backgroundColor = "#4e342e",
+ hoverBackgroundColor = "#8c6659",
+ hoverScale = 1.1
+ ) {
+ super(scene, x, y, text, {
+ fontSize: fontSize,
+ fontFamily: fontFamily,
+ color: color,
+ backgroundColor: backgroundColor,
+ padding: { x: 20, y: 10 },
+ });
+
+ this.sfx = SFX.getInstance(scene);
+
+ this.defaultBackgroundColor = backgroundColor;
+ this.hoverBackgroundColor = hoverBackgroundColor;
+ this.hoverScale = hoverScale;
+
+ scene.add.existing(this);
+ this.setOrigin(0.5);
+ this.setInteractive();
+
+ // Set hover effect
+ this.on("pointerover", () => {
+ this.setStyle({ backgroundColor: this.hoverBackgroundColor });
+ this.setScale(this.hoverScale);
+ });
+ this.on("pointerout", () => {
+ this.setStyle({ backgroundColor: this.defaultBackgroundColor });
+ this.setScale(1);
+ });
+
+ this.on("pointerdown", () => {
+ callback();
+ this.sfx.play("pop-click-1");
+ });
+ }
+}
diff --git a/src/objects/Game.ts b/src/objects/Game.ts
new file mode 100644
index 00000000..0af576ef
--- /dev/null
+++ b/src/objects/Game.ts
@@ -0,0 +1,207 @@
+import Phaser from "phaser";
+import Objective from "./Objective";
+import Board from "./Board";
+
+export default class Game {
+ lvl: number;
+ name: string;
+ tileTypes: string[];
+ tileSize: number;
+ boardSize: number;
+ timeLimitSeconds: number;
+ numInitialSwaps: number;
+ numLives: number;
+ objectives: Objective[] = [];
+ objectivesNum: number;
+ isLocked: boolean;
+
+ constructor(
+ lvl: number,
+ name: string,
+ tileTypes: string[],
+ boardSize: number,
+ timeLimitSeconds?: number,
+ numLives?: number,
+ numInitialSwaps?: number,
+ objectivesNum?: number,
+ isLocked?: boolean
+ ) {
+ this.lvl = lvl;
+ this.name = name;
+ this.tileTypes = tileTypes;
+ this.boardSize = boardSize;
+ this.timeLimitSeconds = timeLimitSeconds || 1000;
+ this.numLives = numLives || 99;
+ this.numInitialSwaps = numInitialSwaps || 99;
+ this.objectives = [];
+ this.objectivesNum = objectivesNum || 3;
+ this.isLocked = isLocked !== undefined ? isLocked : true;
+ }
+
+ startGame(scene: Phaser.Scene) {
+ scene.scene.start("MainGameScene", this);
+ }
+ startTutorial(scene: Phaser.Scene) {
+ scene.scene.start("TutorialScene", this);
+ }
+
+ createBoard(scene: Phaser.Scene) {
+ return new Board(scene, this.boardSize, this.tileTypes, this.name);
+ }
+
+ addObjective(objective: Objective) {
+ this.objectives.push(objective);
+ }
+ // this.gameData.addObjective(
+ // new Objective("One AND tile", 4, (tileTypes: string[]) => {
+ // const andTiles = tileTypes.filter(
+ // (tileType) => tileType === "andTile"
+ // );
+ // return andTiles.length >= 1;
+ // })
+ // );
+ addRandomSimpleObjectives(n: number) {
+ for (let i = 0; i < n; i++) {
+ const targetTile =
+ this.tileTypes[
+ Math.floor(Math.random() * this.tileTypes.length)
+ ];
+ const formattedTileType = targetTile
+ .replace("Tile", "")
+ .toUpperCase();
+ let maxNumTiles = (this.boardSize - 2 + (this.boardSize % 2)) / 2;
+ if (
+ targetTile == "notTile" ||
+ targetTile == "leftParenTile" ||
+ targetTile == "rightParenTile"
+ ) {
+ maxNumTiles = (maxNumTiles - (maxNumTiles % 2)) / 2;
+ }
+ const numTiles = Math.floor(Math.random() * maxNumTiles) + 1;
+ const objective = new Objective(
+ `${numTiles} ${formattedTileType} tile(s)`,
+ 3,
+ (tileTypes: string[]) => {
+ const tiles = tileTypes.filter(
+ (tileType) => tileType === targetTile
+ );
+ return tiles.length >= numTiles;
+ }
+ );
+ this.addObjective(objective);
+ }
+ }
+ addRandomComplexObjectives(
+ numObjectives: number,
+ numCompletions: number = 3
+ ) {
+ for (let i = 0; i < numObjectives; i++) {
+ const targetTileTypes: string[] = [];
+ const numTiles: number[] = [];
+ let maxNumTiles = (this.boardSize - 2 + (this.boardSize % 2)) / 2;
+ let totalNumTiles = 0;
+
+ while (totalNumTiles < maxNumTiles) {
+ maxNumTiles = (this.boardSize - 2 + (this.boardSize % 2)) / 2;
+ const targetTile =
+ this.tileTypes[
+ Math.floor(Math.random() * this.tileTypes.length)
+ ];
+ if (
+ targetTile == "notTile" ||
+ targetTile == "leftParenTile" ||
+ targetTile == "rightParenTile"
+ ) {
+ maxNumTiles = (maxNumTiles - (maxNumTiles % 2)) / 2;
+ }
+ const remainingTiles = maxNumTiles - totalNumTiles;
+ const currNumTiles =
+ Math.floor(Math.random() * remainingTiles) + 1;
+
+ const existingIndex = targetTileTypes.indexOf(targetTile);
+ if (existingIndex !== -1) {
+ numTiles[existingIndex] += currNumTiles;
+ } else {
+ targetTileTypes.push(targetTile);
+ numTiles.push(currNumTiles);
+ }
+
+ totalNumTiles += currNumTiles;
+ }
+
+ const objectiveDescription = `${numTiles
+ .map(
+ (num, index) =>
+ `${num} ${targetTileTypes[index]
+ .replace("Tile", "")
+ .toUpperCase()}`
+ )
+ .join(", ")} tile(s)`;
+
+ const objective = new Objective(
+ objectiveDescription,
+ numCompletions,
+ (tileTypes: string[]) => {
+ return targetTileTypes.every((targetTile, index) => {
+ const tiles = tileTypes.filter(
+ (tileType) => tileType === targetTile
+ );
+ return tiles.length >= numTiles[index];
+ });
+ }
+ );
+
+ this.addObjective(objective);
+ }
+ }
+ isCompleted() {
+ if (this.objectives.length === 0) {
+ return false;
+ }
+ return this.objectives.every((objective) => objective.isCompleted());
+ }
+
+ resetObjectives() {
+ this.objectives = [];
+ }
+
+ handleObjectives(scene: Phaser.Scene, objectives: Objective[]) {
+ if (objectives.length === 0) {
+ return;
+ }
+
+ // Display objectives
+ scene.add.text(10, 180, "Objectives", {
+ fontSize: "32px",
+ color: "#000",
+ });
+
+ const objectiveSpacing = 60;
+
+ scene.add.text(10, 220, "Make a", {
+ fontSize: "25px",
+ color: "#000",
+ });
+
+ scene.add.text(100, 220, " TRUE ", {
+ fontSize: "25px",
+ color: "#006400",
+ fontStyle: "bold",
+ });
+
+ scene.add.text(10, 240, "statement with:", {
+ fontSize: "25px",
+ color: "#000",
+ });
+
+ let index = 0;
+ objectives.forEach((objective) => {
+ objective.createObjectiveText(
+ scene,
+ 10,
+ 270 + objectiveSpacing * index
+ );
+ index++;
+ });
+ }
+}
diff --git a/src/objects/Objective.ts b/src/objects/Objective.ts
new file mode 100644
index 00000000..5f965d95
--- /dev/null
+++ b/src/objects/Objective.ts
@@ -0,0 +1,67 @@
+import Phaser from "phaser";
+
+type ObjectiveCheckFunction = (tileType: string[]) => boolean;
+
+export default class Objective {
+ private progressionCount: number;
+ private currentProgress: number;
+ private objectiveString: string;
+ private checkCondition: (tileTypes: string[]) => boolean;
+ public objectiveText: Phaser.GameObjects.Text;
+
+ constructor(
+ objectiveString: string,
+ progressionCount: number,
+ checkCondition: ObjectiveCheckFunction = () => false
+ ) {
+ this.objectiveString = objectiveString;
+ this.checkCondition = checkCondition;
+ this.progressionCount = progressionCount;
+ this.currentProgress = 0;
+ }
+
+ createObjectiveText(scene: Phaser.Scene, x: number = 0, y: number = 0) {
+ const progress = `${this.currentProgress}/${this.progressionCount}`;
+ this.objectiveText = scene.add.text(
+ x,
+ y,
+ `• ${this.objectiveString}: \n (${progress})`,
+ {
+ fontSize: "25px",
+ color: "#000",
+ }
+ );
+ }
+
+ checkObjective(tileTypes: string[], increment: number = 1): boolean {
+ if (this.checkCondition(tileTypes)) {
+ this.currentProgress += increment;
+ this.updateObjectiveText();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ isCompleted() {
+ return this.currentProgress >= this.progressionCount;
+ }
+
+ private updateObjectiveText() {
+ const progress = `${this.currentProgress}/${this.progressionCount}`;
+ const completedText = this.isCompleted() ? ` (Completed)` : "";
+ this.objectiveText.setText(
+ `• ${this.objectiveString}:\n (${progress})${completedText}`
+ );
+
+ if (this.isCompleted()) {
+ this.objectiveText.setStyle({ color: "green" });
+ console.log("Objective complete:", this.objectiveString); //play sound here
+ }
+ }
+
+ reset() {
+ this.currentProgress = 0;
+ this.updateObjectiveText();
+ }
+}
diff --git a/src/objects/PauseMenu.ts b/src/objects/PauseMenu.ts
new file mode 100644
index 00000000..2d8fc82e
--- /dev/null
+++ b/src/objects/PauseMenu.ts
@@ -0,0 +1,88 @@
+import Phaser from "phaser";
+import SFX from "./SFX";
+import Button from "./Button";
+
+export default class PauseMenu extends Phaser.GameObjects.Container {
+ public scene: Phaser.Scene;
+ private mainMenuButton: Phaser.GameObjects.Text;
+ private restartButton: Phaser.GameObjects.Text;
+ private resumeButton: Phaser.GameObjects.Text;
+ private sfx: SFX;
+
+ constructor(scene: Phaser.Scene) {
+ super(scene, 0, 0);
+ this.scene = scene;
+ this.scene.add.existing(this);
+
+ this.sfx = SFX.getInstance(scene);
+
+ // Add pause menu background
+ const pauseMenuBackground = this.scene.add.rectangle(
+ (this.scene.game.config.width as number) * 0.5,
+ (this.scene.game.config.height as number) * 0.5,
+ (this.scene.game.config.width as number) * 0.6,
+ (this.scene.game.config.height as number) * 0.6,
+ 0x000000,
+ 0.8
+ );
+ this.add(pauseMenuBackground);
+
+ const centerWidth = (this.scene.game.config.width as number) * 0.5;
+ const centerHeight = (this.scene.game.config.height as number) * 0.5;
+ const spacing = (this.scene.game.config.height as number) * 0.1;
+ let index = -1;
+ // Add pause menu text
+ const pauseMenuText = this.scene.add.text(
+ centerWidth,
+ centerHeight + spacing * index,
+ "Paused",
+ { fontSize: "48px", color: "#ffffff" }
+ );
+ pauseMenuText.setOrigin(0.5);
+ this.add(pauseMenuText);
+
+ index++;
+ this.resumeButton = new Button(
+ this.scene,
+ centerWidth,
+ centerHeight + spacing * index,
+ "Resume",
+ () => {
+ this.togglePauseMenu();
+ }
+ );
+ this.add(this.resumeButton);
+
+ // Add Main Menu button
+ index++;
+ this.mainMenuButton = new Button(
+ this.scene,
+ centerWidth,
+ centerHeight + spacing * index,
+ "Main Menu",
+ () => {
+ this.scene.scene.start("MenuScene");
+ }
+ );
+ this.add(this.mainMenuButton);
+
+ index++;
+ this.restartButton = new Button(
+ this.scene,
+ centerWidth,
+ centerHeight + spacing * index,
+ "Restart",
+ () => {
+ this.sfx.play("crumple-paper-1");
+ this.scene.scene.restart();
+ }
+ );
+ this.add(this.restartButton);
+
+ this.setDepth(1000); // Ensure the pause menu is on top
+ }
+
+ public togglePauseMenu() {
+ this.setVisible(!this.visible);
+ }
+}
diff --git a/src/objects/SFX.ts b/src/objects/SFX.ts
new file mode 100644
index 00000000..d7c4afed
--- /dev/null
+++ b/src/objects/SFX.ts
@@ -0,0 +1,77 @@
+import Phaser from "phaser";
+
+export default class SFX {
+ private static SFXInstance: SFX | undefined;
+ private backgroundMusic: Phaser.Sound.HTML5AudioSound | undefined;
+
+ private scene: Phaser.Scene;
+ private sfxAudioKeys = [
+ "click-1",
+ "crumple-paper-1",
+ "incorrect-1",
+ "pop-1",
+ "pop-click-1",
+ "slide-1",
+ "slide-2",
+ "sweep-1",
+ ];
+ private sfxAudioMap: Map;
+
+ private constructor(scene: Phaser.Scene) {
+ this.scene = scene;
+ this.sfxAudioMap = new Map();
+ }
+
+ preload() {
+ this.sfxAudioKeys.forEach((key) => {
+ this.scene.load.audio(key, `assets/sfx/${key}.wav`);
+ });
+ }
+
+ create() {
+ this.sfxAudioKeys.forEach((key) => {
+ this.sfxAudioMap.set(
+ key,
+ this.scene.sound.add(key) as Phaser.Sound.HTML5AudioSound
+ );
+ });
+ if (!this.backgroundMusic) {
+ this.backgroundMusic = this.scene.sound.add("backgroundMusic", {
+ loop: true,
+ volume: 0.4,
+ }) as Phaser.Sound.HTML5AudioSound;
+ this.backgroundMusic.play();
+ }
+ }
+
+ play(key: string) {
+ const sound = this.sfxAudioMap.get(key);
+ if (sound) {
+ sound.play();
+ } else {
+ console.warn(`Sound effect with key '${key}' not found.`);
+ }
+ }
+
+ public static getInstance(scene: Phaser.Scene): SFX {
+ // This condition ensures a new instance is created only if one doesn't already exist.
+ if (!SFX.SFXInstance) {
+ SFX.SFXInstance = new SFX(scene);
+ }
+ return SFX.SFXInstance;
+ }
+
+ getMusic(): Phaser.Sound.HTML5AudioSound {
+ return this.backgroundMusic as Phaser.Sound.HTML5AudioSound;
+ }
+
+ getSFX(): Map {
+ return this.sfxAudioMap;
+ }
+
+ setSFXVolume(volume: number) {
+ this.sfxAudioMap.forEach((sfx) => {
+ sfx.setVolume(volume);
+ });
+ }
+}
diff --git a/src/objects/Sketch.ts b/src/objects/Sketch.ts
new file mode 100644
index 00000000..9c0debbb
--- /dev/null
+++ b/src/objects/Sketch.ts
@@ -0,0 +1,43 @@
+import Phaser from "phaser";
+export default class Sketch extends Phaser.GameObjects.Sprite {
+ constructor(
+ scene: Phaser.Scene,
+ x: number,
+ y: number,
+ size: number,
+ minScale = 0.8,
+ maxScale = 1.2,
+ minTilt = -45,
+ maxTilt = 45
+ ) {
+ // Generate a random sketch number between 1 and 12
+ const sketchNum = Phaser.Math.Between(1, 12);
+ const sketchKey = `sketch-${sketchNum}`;
+
+ // Generate a random scale and rotation
+ super(scene, x, y, sketchKey, 0);
+
+ const initialScale = size / this.width;
+ this.setScale(initialScale);
+
+ const scale = Phaser.Math.FloatBetween(minScale, maxScale);
+ const tilt = Phaser.Math.FloatBetween(minTilt, maxTilt);
+
+ // Call the parent constructor with the random sketch key and scale
+
+ // Set the random rotation
+ this.setAngle(tilt);
+ this.setScale(scale * initialScale);
+
+ // Add the sketch to the scene
+ scene.add.existing(this);
+ }
+
+ static preload(scene: Phaser.Scene) {
+ // Preload all the sketch images
+ for (let i = 1; i <= 12; i++) {
+ const sketchKey = `sketch-${i}`;
+ scene.load.image(sketchKey, `assets/sketches/${sketchKey}.png`);
+ }
+ }
+}
diff --git a/src/objects/Stages.ts b/src/objects/Stages.ts
new file mode 100644
index 00000000..bc933e71
--- /dev/null
+++ b/src/objects/Stages.ts
@@ -0,0 +1,10 @@
+import Game from "./Game";
+export default class Stage {
+ name: string;
+ games: Game[];
+
+ constructor(name: string, games: Game[]) {
+ this.name = name;
+ this.games = games;
+ }
+}
diff --git a/src/objects/Tile.ts b/src/objects/Tile.ts
new file mode 100644
index 00000000..4a2dee77
--- /dev/null
+++ b/src/objects/Tile.ts
@@ -0,0 +1,40 @@
+import Phaser from "phaser";
+export default class Tile extends Phaser.GameObjects.Sprite {
+ public tileType: string;
+ public overlay: Phaser.GameObjects.Sprite;
+
+ constructor(
+ scene: Phaser.Scene,
+ x: number,
+ y: number,
+ tileType: string,
+ tileSize: number
+ ) {
+ super(scene, x, y, tileType);
+ this.tileType = tileType;
+ console.log("new tile added");
+ scene.add.existing(this);
+
+ //overlay
+ this.overlay = scene.add.sprite(x, y, tileType + "Select");
+ this.overlay.setOrigin(0, 0);
+ this.overlay.setScale(
+ tileSize / this.overlay.width,
+ tileSize / this.overlay.height
+ );
+
+ this.overlay.setVisible(false);
+ }
+
+ public setPosition(x?: number | undefined, y?: number | undefined): this {
+ return super.setPosition(x, y);
+ }
+
+ public showOverlay() {
+ this.overlay.setVisible(true);
+ }
+
+ public hideOverlay() {
+ this.overlay.setVisible(false);
+ }
+}
diff --git a/src/objects/fpsText.ts b/src/objects/fpsText.ts
deleted file mode 100644
index b1a2a3d8..00000000
--- a/src/objects/fpsText.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import Phaser from "phaser";
-export default class FpsText extends Phaser.GameObjects.Text {
- constructor(scene: Phaser.Scene) {
- super(scene, 10, 10, "", { color: "black", fontSize: "28px" });
- scene.add.existing(this);
- this.setOrigin(0);
- }
-
- public update() {
- this.setText(`fps: ${Math.floor(this.scene.game.loop.actualFps)}`);
- }
-}
diff --git a/src/objects/phaserLogo.ts b/src/objects/phaserLogo.ts
deleted file mode 100644
index 84324120..00000000
--- a/src/objects/phaserLogo.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import Phaser from "phaser";
-
-export default class PhaserLogo extends Phaser.Physics.Arcade.Sprite {
- constructor(scene: Phaser.Scene, x: number, y: number) {
- super(scene, x, y, "phaser-logo");
- scene.add.existing(this);
- scene.physics.add.existing(this);
-
- this.setCollideWorldBounds(true)
- .setBounce(0.6)
- .setInteractive()
- .on("pointerdown", () => {
- this.setVelocityY(-400);
- });
- }
-}
diff --git a/src/objects/victorySketch.ts b/src/objects/victorySketch.ts
new file mode 100644
index 00000000..964962d0
--- /dev/null
+++ b/src/objects/victorySketch.ts
@@ -0,0 +1,54 @@
+import Phaser from "phaser";
+export default class victorySketch extends Phaser.GameObjects.Sprite {
+ constructor(
+ scene: Phaser.Scene,
+ x: number,
+ y: number,
+ size: number,
+ minScale = 0.8,
+ maxScale = 2,
+ minTilt = -45,
+ maxTilt = 45
+ ) {
+ // Generate a random sketch number between 1 and 25
+ const sketchNum = Phaser.Math.Between(1, 25);
+ const sketchKey = `victorySketch${sketchNum}`;
+
+ // Generate a random scale and rotation
+ super(scene, x, y, sketchKey, 0);
+ const initialScale = size / this.width;
+ this.setScale(initialScale);
+
+ const scale = Phaser.Math.FloatBetween(minScale, maxScale);
+ const tilt = Phaser.Math.FloatBetween(minTilt, maxTilt);
+
+ // Call the parent constructor with the random sketch key and scale
+
+ // Set the random rotation
+ this.setAngle(tilt);
+ this.setScale(scale * initialScale);
+
+ while (this.displayHeight > 500) {
+ let scale = this.scaleX;
+ this.setScale(scale * 0.9);
+ }
+ while (this.displayHeight < 250) {
+ let scale = this.scaleX;
+ this.setScale(scale * 1.1);
+ }
+ console.log(this.displayHeight);
+ // Add the sketch to the scene
+ scene.add.existing(this);
+ }
+
+ static preload(scene: Phaser.Scene) {
+ // Preload all the sketch images
+ for (let i = 1; i <= 25; i++) {
+ const sketchKey = `victorySketch${i}`;
+ scene.load.image(
+ sketchKey,
+ `assets/victorySketches/${sketchKey}.png`
+ );
+ }
+ }
+}
diff --git a/src/scenes/baseScene.ts b/src/scenes/baseScene.ts
new file mode 100644
index 00000000..0e2b4d62
--- /dev/null
+++ b/src/scenes/baseScene.ts
@@ -0,0 +1,214 @@
+import Phaser from "phaser";
+import PauseMenu from "../objects/PauseMenu";
+import { DirectionType } from "../types/DirectionType";
+import Board from "../objects/Board";
+import Game from "../objects/Game";
+import SFX from "../objects/SFX";
+
+export default class baseScene extends Phaser.Scene {
+ protected score: number = 0;
+ protected scoreText: Phaser.GameObjects.Text;
+ protected timerValue: number = 1000;
+ protected timerText: Phaser.GameObjects.Text;
+ protected livesValue: number = 3;
+ protected livesText: Phaser.GameObjects.Text;
+ protected restartText: Phaser.GameObjects.Text;
+ protected swapsValue: number = 3;
+ protected swapsText: Phaser.GameObjects.Text;
+ protected pauseMenu: PauseMenu | null = null;
+ protected gameBoard: Board;
+ protected gameData: Game;
+ protected anyValidSolutions: boolean | undefined = false;
+ protected sfx: SFX;
+
+ handleKeydown(event: KeyboardEvent) {
+ switch (event.key) {
+ case "Escape":
+ if (this.pauseMenu) {
+ this.pauseMenu.togglePauseMenu();
+ }
+ break;
+ case "Enter":
+ if (!this.anyValidSolutions) {
+ this.gameBoard.regenerateBoard();
+ this.anyValidSolutions =
+ this.gameBoard.isPossibleSolution();
+ this.restartText.visible = !this.anyValidSolutions;
+ this.sfx.play("crumple-paper-1");
+ }
+ break;
+ }
+
+ if (!this.gameBoard.isTileSelected()) return;
+
+ let [currentRow, currentCol] = this.gameBoard.findSelectedTile();
+
+ if (currentRow === -1 || currentCol === -1) {
+ // Selected tile not found in the board, handle this case if needed
+ }
+
+ let newRow = currentRow;
+ let newCol = currentCol;
+
+ //console.log(event.key);
+ switch (event.key) {
+ case "ArrowUp":
+ case "w":
+ newRow = Math.max(currentRow - 1, 0);
+ break;
+ case "ArrowDown":
+ case "s":
+ newRow = Math.min(
+ currentRow + 1,
+ this.gameBoard.getBoardSize() - 1
+ );
+ break;
+ case "ArrowLeft":
+ case "a":
+ newCol = Math.max(currentCol - 1, 0);
+ break;
+ case "ArrowRight":
+ case "d":
+ newCol = Math.min(
+ currentCol + 1,
+ this.gameBoard.getBoardSize() - 1
+ );
+ break;
+ case "r":
+ this.gameBoard.selectTiles(currentRow, DirectionType.ROW);
+ break;
+ case "c":
+ this.gameBoard.selectTiles(currentCol, DirectionType.COL);
+ break;
+ case "Enter":
+ if (
+ this.gameBoard.handleRowColCheck(
+ currentRow,
+ currentCol,
+ () => {}
+ )
+ ) {
+ this.addScore(10);
+ this.addSwaps(this.getGameBoard().getBoardSize());
+ if (
+ this.checkObjectives(
+ this.gameBoard.lastRemovedTileTypes
+ )
+ ) {
+ this.completeGame();
+ }
+ this.anyValidSolutions =
+ this.gameBoard.isPossibleSolution();
+ this.restartText.visible = !this.anyValidSolutions;
+ } else {
+ this.gameBoard.handleRowColCheck(
+ currentRow,
+ currentCol,
+ () => {
+ this.subtractLife();
+ }
+ );
+ }
+ break;
+ }
+
+ if (
+ newRow >= 0 &&
+ newRow < this.gameBoard.getBoardSize() &&
+ newCol >= 0 &&
+ newCol < this.gameBoard.getBoardSize()
+ ) {
+ if (newRow !== currentRow || newCol !== currentCol) {
+ this.gameBoard.swapTiles(
+ currentRow,
+ currentCol,
+ newRow,
+ newCol,
+ () => {
+ this.subtractSwap(); // will be called after the swap animation is done
+ }
+ );
+ }
+ }
+ }
+
+ private checkObjectives(tileTypes: string[]): boolean {
+ if (this.gameData.objectives.length == 0) {
+ return false;
+ }
+ let allTrue = true;
+ this.gameData.objectives.forEach((objective) => {
+ objective.checkObjective(tileTypes);
+ if (!objective.isCompleted()) {
+ allTrue = false;
+ }
+ });
+
+ return allTrue;
+ }
+
+ private addScore(score: number) {
+ this.score += score;
+ this.scoreText.setText(`Score: ${this.score}`);
+
+ const scoreFeedback = this.add.text(
+ this.scoreText.x,
+ this.scoreText.y,
+ `+${score}`
+ );
+
+ this.tweens.add({
+ targets: scoreFeedback,
+ y: this.scoreText.y - 50,
+ alpha: 0, // Fade out
+ ease: "Cubic.easeOut",
+ duration: 1000,
+ onComplete: () => {
+ console.log("score added");
+ scoreFeedback.destroy();
+ },
+ });
+ }
+ private setScore(score: number) {
+ this.score += score;
+ this.scoreText.setText(`Score: ${this.score}`);
+ }
+ private subtractLife() {
+ this.livesValue--;
+ this.livesText.setText(`Lives: ${this.livesValue}`);
+ }
+ private addSwaps(swaps: number) {
+ this.swapsValue += swaps;
+ this.swapsText.setText(`Swaps: ${this.swapsValue}`);
+ }
+ private subtractSwap() {
+ this.swapsValue--;
+ this.swapsText.setText(`Swaps: ${this.swapsValue}`);
+ }
+
+ protected getGameBoard(): Board {
+ return this.gameBoard;
+ }
+ protected getScore(): number {
+ return this.score;
+ }
+ protected getPauseMenu(): PauseMenu | null {
+ return this.pauseMenu;
+ }
+ protected getScoreText(): Phaser.GameObjects.Text {
+ return this.scoreText;
+ }
+ protected endGame(message: string) {
+ console.log("game over:");
+ //create game over screen
+ this.scene.start("GameOverScene", {
+ lastScene: this.scene.key,
+ message: message,
+ });
+ }
+ protected completeGame() {
+ console.log("game won:");
+ //create game victory screen
+ this.scene.start("GameVictoryScene"); //replace this with victory
+ }
+}
diff --git a/src/scenes/controlScene.ts b/src/scenes/controlScene.ts
new file mode 100644
index 00000000..231d441f
--- /dev/null
+++ b/src/scenes/controlScene.ts
@@ -0,0 +1,77 @@
+import Background from "../objects/Background";
+import SFX from "../objects/SFX";
+import Game from "../objects/Game";
+import baseScene from "./baseScene";
+import Button from "../objects/Button";
+import Sketch from "../objects/Sketch";
+
+export default class ControlScene extends baseScene {
+ constructor() {
+ super({ key: "ControlScene" });
+ this.sfx = SFX.getInstance(this);
+ }
+
+ init(data: Game) {
+ this.gameData = data;
+ }
+
+ create() {
+ const backgroundImage = Background.getInstance(this, "background");
+ backgroundImage.create();
+ const screenCenterX = (this.game.config.width as number) * 0.5;
+ const screenCenterY = (this.game.config.height as number) * 0.5;
+
+ // Add title text
+ const titleText = this.add.text(
+ screenCenterX,
+ screenCenterY * 0.3,
+ "Boolean Bonanza! Controls",
+ {
+ fontSize: "36px",
+ fontFamily: "Arial",
+ color: "#000000",
+ }
+ );
+ titleText.setOrigin(0.5);
+
+ // Add controls text
+ const controlsText = this.add.text(
+ screenCenterX,
+ screenCenterY,
+ `Controls:
+ Arrow Keys or WASD: Move selected tile
+ R: Select entire row
+ C: Select entire column
+ Enter: Check selected row or column
+ Esc: Pause`,
+ {
+ fontSize: "24px",
+ fontFamily: "Arial",
+ color: "#000000",
+ align: "center",
+ }
+ );
+ controlsText.setOrigin(0.5, 0.5);
+
+ // Add a back button
+ const backButton = new Button(
+ this,
+ 70,
+ 70,
+ "Back",
+ () => {
+ this.scene.start("MenuScene");
+ },
+ "24px"
+ );
+ backButton.setDepth(1);
+
+ const maxWidth = this.game.config.width as number;
+ const maxHeight = this.game.config.height as number;
+
+ new Sketch(this, 150, 150, 120);
+ new Sketch(this, maxWidth - 150, 200, 120);
+ new Sketch(this, maxWidth - 150, maxHeight - 150, 120);
+ new Sketch(this, 100, maxHeight - 150, 120);
+ }
+}
diff --git a/src/scenes/creditsScene.ts b/src/scenes/creditsScene.ts
new file mode 100644
index 00000000..07886b5c
--- /dev/null
+++ b/src/scenes/creditsScene.ts
@@ -0,0 +1,54 @@
+import Phaser from "phaser";
+import Background from "../objects/Background";
+import SFX from "../objects/SFX";
+import Button from "../objects/Button";
+import Sketch from "../objects/Sketch";
+
+export default class CreditsScene extends Phaser.Scene {
+ private sfx: SFX;
+
+ constructor() {
+ super({ key: "CreditsScene" });
+ this.sfx = SFX.getInstance(this);
+ }
+
+ create() {
+ const backgroundImage = Background.getInstance(this, "background");
+ backgroundImage.create();
+ //this.sfx.create();
+
+ // Add credits text or any other content you want to display in the CreditsScene
+ const creditsText = this.add.text(
+ (this.game.config.width as number) * 0.5,
+ (this.game.config.height as number) * 0.3,
+ "Credits\n\nDeveloped by: Patrick Tiamson & Angelo Ramos\nArtwork by: Angelo Ramos\nMusic & SFX by: Patrick Tiamson\n\n\nBackground Image from iStock",
+ {
+ fontSize: "32px",
+ fontFamily: "Arial",
+ color: "#000000",
+ align: "center",
+ }
+ );
+ creditsText.setOrigin(0.5);
+
+ const backButton = new Button(
+ this,
+ 70,
+ 70,
+ "Back",
+ () => {
+ this.scene.start("MenuScene");
+ },
+ "24px"
+ );
+ backButton.setDepth(1);
+
+ const maxWidth = this.game.config.width as number;
+ const maxHeight = this.game.config.height as number;
+
+ new Sketch(this, 150, 150, 120);
+ new Sketch(this, maxWidth - 150, 200, 120);
+ new Sketch(this, maxWidth - 150, maxHeight - 150, 120);
+ new Sketch(this, 100, maxHeight - 150, 120);
+ }
+}
diff --git a/src/scenes/freeplayScene.ts b/src/scenes/freeplayScene.ts
new file mode 100644
index 00000000..5a5c5a47
--- /dev/null
+++ b/src/scenes/freeplayScene.ts
@@ -0,0 +1,429 @@
+import Phaser from "phaser";
+import Background from "../objects/Background";
+import SFX from "../objects/SFX";
+import Button from "../objects/Button";
+import Game from "../objects/Game";
+import Tile from "../objects/Tile"; // Import the Tile class
+import Sketch from "../objects/Sketch";
+
+export default class FreeplayScene extends Phaser.Scene {
+ private sfx: SFX;
+ private gameButtonsShown: Phaser.GameObjects.Text[] = [];
+ private tileButtonsShown: Tile[] = []; // Use Tile class
+
+ constructor() {
+ super({ key: "FreeplayScene" });
+ this.sfx = SFX.getInstance(this);
+ }
+
+ create() {
+ const backgroundImage = Background.getInstance(this, "background");
+ backgroundImage.create();
+
+ const screenWidth = this.game.config.width as number;
+ const screenHeight = this.game.config.height as number;
+
+ const settingsX = screenWidth * 0.2;
+ const settingsY = screenHeight * 0.2;
+ const settingsSpacing = 100;
+ const valueOffset = 250;
+ const buttonOffset = 350;
+
+ // Board size
+ this.add
+ .text(screenWidth * 0.5, screenHeight * 0.1, "Freeplay", {
+ fontSize: "48px",
+ fontFamily: "Arial",
+ color: "#000000",
+ })
+ .setOrigin(0.5, 0.5);
+
+ const boardSizeLabel = this.createFreeplayTextSettings(
+ settingsX,
+ settingsY,
+ "Board Size:"
+ );
+ this.gameButtonsShown.push(boardSizeLabel);
+
+ let boardSize = 5;
+ const boardSizeValue = this.add.text(
+ settingsX + valueOffset,
+ settingsY,
+ `${boardSize}`,
+ {
+ fontSize: "32px",
+ fontFamily: "Arial",
+ color: "#000000",
+ }
+ );
+ this.gameButtonsShown.push(boardSizeValue);
+
+ const boardSizeDecrement = this.createIncrementDecrementButton(
+ settingsX,
+ settingsY,
+ buttonOffset,
+ "-"
+ );
+ boardSizeDecrement.setInteractive();
+ boardSizeDecrement.on("pointerdown", () => {
+ boardSize = Math.max(boardSize - 1, 3);
+ boardSizeValue.setText(`${boardSize}`);
+ this.sfx.play("pop-click-1");
+ });
+ this.gameButtonsShown.push(boardSizeDecrement);
+
+ const boardSizeIncrement = this.createIncrementDecrementButton(
+ settingsX,
+ settingsY,
+ buttonOffset + 50,
+ "+"
+ );
+ boardSizeIncrement.setInteractive();
+ boardSizeIncrement.on("pointerdown", () => {
+ boardSize = Math.min(boardSize + 1, 15);
+ boardSizeValue.setText(`${boardSize}`);
+ this.sfx.play("pop-click-1");
+ });
+ this.gameButtonsShown.push(boardSizeIncrement);
+
+ // Tiles
+ const tileLabel = this.createFreeplayTextSettings(
+ settingsX,
+ settingsY + settingsSpacing,
+ "Tiles:"
+ );
+ this.gameButtonsShown.push(tileLabel);
+
+ const tileSprites = [
+ "trueTile",
+ "falseTile",
+ "andTile",
+ "orTile",
+ "leftParenTile",
+ "notTile",
+ "xorTile",
+ "rightParenTile",
+ ];
+ tileSprites.forEach((sprite, index) => {
+ const tileButton = new Tile(
+ this,
+ settingsX + (index % 4) * 60 + 300,
+ settingsY + settingsSpacing + Math.floor(index / 4) * 60 - 45,
+ sprite,
+ 64
+ );
+ tileButton.setScale(64 / tileButton.height);
+ tileButton.showOverlay();
+ tileButton.setOrigin(0, 0);
+ tileButton.setInteractive();
+ tileButton.on("pointerdown", () => {
+ if (tileButton.overlay.visible) {
+ tileButton.hideOverlay();
+ } else {
+ tileButton.showOverlay();
+ }
+ this.sfx.play("pop-click-1");
+ });
+ this.tileButtonsShown.push(tileButton);
+ });
+
+ // Time limit
+ const timeLimitLabel = this.createFreeplayTextSettings(
+ settingsX,
+ settingsY + 2 * settingsSpacing,
+ "Time Limit (s):"
+ );
+ this.gameButtonsShown.push(timeLimitLabel);
+
+ let timeLimit = 180;
+ const timeLimitValue = this.add.text(
+ settingsX + valueOffset,
+ settingsY + 2 * settingsSpacing,
+ `${timeLimit}`,
+ {
+ fontSize: "32px",
+ fontFamily: "Arial",
+ color: "#000000",
+ }
+ );
+ this.gameButtonsShown.push(timeLimitValue);
+
+ const timeLimitDecrement = this.createIncrementDecrementButton(
+ settingsX,
+ settingsY + 2 * settingsSpacing,
+ buttonOffset,
+ "-"
+ );
+ timeLimitDecrement.setInteractive();
+ timeLimitDecrement.on("pointerdown", () => {
+ timeLimit = Math.max(timeLimit - 10, 0);
+ timeLimitValue.setText(`${timeLimit}`);
+ this.sfx.play("pop-click-1");
+ });
+ this.gameButtonsShown.push(timeLimitDecrement);
+
+ const timeLimitIncrement = this.createIncrementDecrementButton(
+ settingsX,
+ settingsY + 2 * settingsSpacing,
+ buttonOffset + 50,
+ "+"
+ );
+ timeLimitIncrement.setInteractive();
+ timeLimitIncrement.on("pointerdown", () => {
+ timeLimit += 10;
+ timeLimitValue.setText(`${timeLimit}`);
+ this.sfx.play("pop-click-1");
+ });
+ this.gameButtonsShown.push(timeLimitIncrement);
+
+ // Number of lives
+ const livesLabel = this.createFreeplayTextSettings(
+ settingsX,
+ settingsY + 3 * settingsSpacing,
+ "Number of Lives:"
+ );
+ this.gameButtonsShown.push(livesLabel);
+
+ let lives = 3;
+ const livesValue = this.add.text(
+ settingsX + valueOffset,
+ settingsY + 3 * settingsSpacing,
+ `${lives}`,
+ {
+ fontSize: "32px",
+ fontFamily: "Arial",
+ color: "#000000",
+ }
+ );
+ this.gameButtonsShown.push(livesValue);
+
+ const livesDecrement = this.createIncrementDecrementButton(
+ settingsX,
+ settingsY + 3 * settingsSpacing,
+ buttonOffset,
+ "-"
+ );
+ livesDecrement.setInteractive();
+ livesDecrement.on("pointerdown", () => {
+ lives = Math.max(lives - 1, 1);
+ livesValue.setText(`${lives}`);
+ this.sfx.play("pop-click-1");
+ });
+ this.gameButtonsShown.push(livesDecrement);
+
+ const livesIncrement = this.createIncrementDecrementButton(
+ settingsX,
+ settingsY + 3 * settingsSpacing,
+ buttonOffset + 50,
+ "+"
+ );
+ livesIncrement.setInteractive();
+ livesIncrement.on("pointerdown", () => {
+ lives++;
+ livesValue.setText(`${lives}`);
+ this.sfx.play("pop-click-1");
+ });
+ this.gameButtonsShown.push(livesIncrement);
+
+ // Number of initial swaps
+ const swapsLabel = this.createFreeplayTextSettings(
+ settingsX,
+ settingsY + 4 * settingsSpacing,
+ "Initial Swaps:"
+ );
+ this.gameButtonsShown.push(swapsLabel);
+
+ let initialSwaps = 7;
+ const swapsValue = this.add.text(
+ settingsX + valueOffset,
+ settingsY + 4 * settingsSpacing,
+ `${initialSwaps}`,
+ {
+ fontSize: "32px",
+ fontFamily: "Arial",
+ color: "#000000",
+ }
+ );
+ this.gameButtonsShown.push(swapsValue);
+
+ const swapsDecrement = this.createIncrementDecrementButton(
+ settingsX,
+ settingsY + 4 * settingsSpacing,
+ buttonOffset,
+ "-"
+ );
+ swapsDecrement.setInteractive();
+ swapsDecrement.on("pointerdown", () => {
+ initialSwaps = Math.max(initialSwaps - 1, 0);
+ swapsValue.setText(`${initialSwaps}`);
+ this.sfx.play("pop-click-1");
+ });
+ this.gameButtonsShown.push(swapsDecrement);
+
+ const swapsIncrement = this.createIncrementDecrementButton(
+ settingsX,
+ settingsY + 4 * settingsSpacing,
+ buttonOffset + 50,
+ "+"
+ );
+ swapsIncrement.setInteractive();
+ swapsIncrement.on("pointerdown", () => {
+ initialSwaps++;
+ swapsValue.setText(`${initialSwaps}`);
+ this.sfx.play("pop-click-1");
+ });
+ this.gameButtonsShown.push(swapsIncrement);
+
+ // Number of objectives swaps
+ const objectivesLabel = this.createFreeplayTextSettings(
+ settingsX,
+ settingsY + 5 * settingsSpacing,
+ "# Objectives:"
+ );
+ this.gameButtonsShown.push(objectivesLabel);
+
+ let valueObjectives = 3;
+ const objectivesValue = this.add.text(
+ settingsX + valueOffset,
+ settingsY + 5 * settingsSpacing,
+ `${valueObjectives}`,
+ {
+ fontSize: "32px",
+ fontFamily: "Arial",
+ color: "#000000",
+ }
+ );
+ this.gameButtonsShown.push(objectivesValue);
+
+ const objectivesDecrement = this.createIncrementDecrementButton(
+ settingsX,
+ settingsY + 5 * settingsSpacing,
+ buttonOffset,
+ "-"
+ );
+ objectivesDecrement.setInteractive();
+ objectivesDecrement.on("pointerdown", () => {
+ valueObjectives = Math.max(valueObjectives - 1, 0);
+ objectivesValue.setText(`${valueObjectives}`);
+ this.sfx.play("pop-click-1");
+ });
+ this.gameButtonsShown.push(objectivesDecrement);
+
+ const objectivesIncrement = this.createIncrementDecrementButton(
+ settingsX,
+ settingsY + 5 * settingsSpacing,
+ buttonOffset + 50,
+ "+"
+ );
+ objectivesIncrement.setInteractive();
+ objectivesIncrement.on("pointerdown", () => {
+ valueObjectives++;
+ objectivesValue.setText(`${valueObjectives}`);
+ this.sfx.play("pop-click-1");
+ });
+ this.gameButtonsShown.push(objectivesIncrement);
+
+ const startGameButton = this.add.text(
+ screenWidth - settingsX,
+ screenHeight * 0.5,
+ "Start",
+ {
+ fontSize: "48px",
+ fontFamily: "Arial",
+ color: "#ffffff",
+ backgroundColor: "#4e342e",
+ padding: { x: 20, y: 10 },
+ }
+ );
+ startGameButton.setInteractive();
+ startGameButton.on("pointerdown", () => {
+ this.sfx.play("crumple-paper-1");
+
+ const game = new Game(
+ -1,
+ "Freeplay",
+ this.tileButtonsShown
+ .filter((tile) => tile.overlay.visible)
+ .map((filteredTile) => filteredTile.texture.key),
+ boardSize,
+ timeLimit,
+ lives,
+ initialSwaps,
+ valueObjectives
+ );
+ this.registry.set("currentStage", game.name);
+ this.registry.set("currentGame", game.name);
+ game.startGame(this);
+ });
+ startGameButton.on("pointerover", () => {
+ startGameButton.setStyle({ backgroundColor: "#6d4c41" });
+ startGameButton.setScale(1.1);
+ });
+ startGameButton.on("pointerout", () => {
+ startGameButton.setStyle({ backgroundColor: "#4e342e" });
+ startGameButton.setScale(1);
+ });
+ this.gameButtonsShown.push(startGameButton);
+
+ // Add back button
+ const backButton = new Button(
+ this,
+ 70,
+ 70,
+ "Back",
+ () => {
+ this.scene.start("ProgressionScene");
+ },
+ "32px"
+ );
+ backButton.setDepth(1);
+
+ const maxWidth = this.game.config.width as number;
+ const maxHeight = this.game.config.height as number;
+
+ new Sketch(this, 90, 90, 90);
+ new Sketch(this, maxWidth - 90, 90, 90);
+ new Sketch(this, maxWidth - 90, maxHeight - 90, 90);
+ new Sketch(this, 90, maxHeight - 90, 90);
+ }
+
+ createIncrementDecrementButton(
+ settingsX: number,
+ settingsY: number,
+ positionX: number,
+ symbol: string
+ ) {
+ const button = this.add.text(settingsX + positionX, settingsY, symbol, {
+ fontSize: "32px",
+ fontFamily: "Arial",
+ color: "#ffffff",
+ backgroundColor: "#4e342e",
+ padding: { x: 10, y: 5 },
+ });
+
+ button.setInteractive();
+ button.on("pointerover", () => {
+ button.setStyle({ backgroundColor: "#6d4c41" });
+ button.setScale(1.1);
+ });
+ button.on("pointerout", () => {
+ button.setStyle({ backgroundColor: "#4e342e" });
+ button.setScale(1);
+ });
+
+ return button;
+ }
+
+ createFreeplayTextSettings(
+ settingsX: number,
+ settingsY: number,
+ text: string
+ ) {
+ const freeplayText = this.add.text(settingsX, settingsY, text, {
+ fontSize: "32px",
+ fontFamily: "Arial",
+ color: "#000000",
+ });
+ return freeplayText;
+ }
+}
diff --git a/src/scenes/gameOverScene.ts b/src/scenes/gameOverScene.ts
new file mode 100644
index 00000000..528350c7
--- /dev/null
+++ b/src/scenes/gameOverScene.ts
@@ -0,0 +1,44 @@
+import Phaser from "phaser";
+import Background from "../objects/Background";
+import Button from "../objects/Button";
+export default class GameOverScene extends Phaser.Scene {
+ private lastScene: string;
+
+ private message: string;
+ constructor() {
+ super({ key: "GameOverScene" });
+ }
+
+ init(data: { lastScene: string; message: string }) {
+ this.lastScene = data.lastScene;
+ this.message = data.message;
+ }
+
+ create() {
+ const backgroundImage = Background.getInstance(
+ this,
+ "crumbleBackground"
+ );
+ backgroundImage.create();
+ const width: number = this.game.config.width as number;
+ const height: number = this.game.config.height as number;
+ this.add.image(width * 0.5, height * 0.3, "gameOver").setOrigin(0.5);
+
+ new Button(this, width * 0.5, height * 0.6, "Retry", () => {
+ if (this.lastScene === "MainGameScene") {
+ this.scene.start("MainGameScene");
+ } else {
+ this.scene.start("TutorialScene");
+ }
+ });
+
+ new Button(this, width * 0.5, height * 0.7, "Menu", () => {
+ this.scene.start("MenuScene");
+ });
+
+ this.add
+ .image(width * 0.8, height * 0.8, "fail")
+ .setOrigin(0.5)
+ .setScale(2);
+ }
+}
diff --git a/src/scenes/gameVictoryScene.ts b/src/scenes/gameVictoryScene.ts
new file mode 100644
index 00000000..9703f61a
--- /dev/null
+++ b/src/scenes/gameVictoryScene.ts
@@ -0,0 +1,135 @@
+import Phaser from "phaser";
+import Background from "../objects/Background";
+import victorySketch from "../objects/victorySketch";
+import Button from "../objects/Button";
+import Stage from "../objects/Stages";
+import Game from "../objects/Game";
+
+export default class GameVictoryScene extends Phaser.Scene {
+ private lastScene: string;
+ private allLevelsCompleted: boolean = false;
+ constructor() {
+ super({ key: "GameVictoryScene" });
+ }
+
+ init() {}
+
+ create() {
+ const width: number = this.game.config.width as number;
+ const height: number = this.game.config.height as number;
+ const backgroundImage = Background.getInstance(this, "background");
+ backgroundImage.create();
+ const completeImage = this.add
+ .image(width * 0.5, height * 0.2, "complete")
+ .setOrigin(0.5);
+
+ this.tweens.add({
+ targets: completeImage,
+ scale: 1.1,
+ duration: 1200,
+ yoyo: true,
+ repeat: -1,
+ ease: "Sine.easeOut",
+ });
+
+ completeImage.setDepth(1);
+
+ new victorySketch(
+ this,
+ width * 0.2,
+ height * 0.6,
+ 200,
+ 1.5,
+ 1.5,
+ -15,
+ 15
+ );
+
+ new victorySketch(
+ this,
+ width * 0.8,
+ height * 0.6,
+ 200,
+ 1.5,
+ 1.5,
+ -15,
+ 15
+ );
+
+ this.allLevelsCompleted = false;
+ const stages = this.registry.get("stages") as Stage[];
+ //console.log(stages);
+ let currentStage = this.registry.get("currentStage") as string;
+ let currentGame = this.registry.get("currentGame") as string;
+
+ // return 0 for stage 1, 1 for stage 2, 2 for stage 3
+ let currentStageIndex = stages.findIndex(
+ (stage) => stage.name === currentStage
+ );
+
+ if (currentStage === "Freeplay") {
+ currentStageIndex = 0;
+ } else {
+ //this code is so bad but its ok
+ for (const stage of stages) {
+ for (const game of stage.games) {
+ if (game.name == currentGame) {
+ let currentLevel = this.registry.get("level") || 0;
+ console.log("currentLevel", currentLevel);
+ if (game.lvl == currentLevel) {
+ this.registry.set("level", ++currentLevel);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // return 0 for game 1, 1 for game 2, 2 for game 3
+ let currentGameIndex = stages[currentStageIndex].games.findIndex(
+ (game) => game.name === currentGame
+ );
+
+ let nextGame: Game;
+ if (currentGameIndex < stages[currentStageIndex].games.length - 1) {
+ nextGame = stages[currentStageIndex].games[currentGameIndex + 1];
+ } else if (currentStageIndex < stages.length - 1) {
+ currentStageIndex++;
+ currentGameIndex = 0;
+ nextGame = stages[currentStageIndex].games[currentGameIndex];
+ } else {
+ this.allLevelsCompleted = true;
+ }
+
+ if (this.allLevelsCompleted) {
+ this.add
+ .text(width * 0.5, height * 0.5, "All Levels Completed", {
+ font: "48px Arial",
+ color: "#000000",
+ align: "center",
+ })
+ .setOrigin(0.5, 0.5);
+ } else if (currentStage === "Freeplay") {
+ this.add
+ .text(width * 0.5, height * 0.5, "", {
+ font: "48px Arial",
+ color: "#000000",
+ align: "center",
+ })
+ .setOrigin(0.5, 0.5);
+ } else {
+ new Button(this, width * 0.5, height * 0.6, "Next Level", () => {
+ this.registry.set("currentGame", nextGame.name);
+ this.registry.set(
+ "currentStage",
+ stages[currentStageIndex].name
+ );
+ this.scene.start("MainGameScene", nextGame);
+ });
+ }
+
+ new Button(this, width * 0.5, height * 0.7, "Menu", () => {
+ this.scene.start("MenuScene");
+ });
+ }
+}
diff --git a/src/scenes/mainScene.ts b/src/scenes/mainScene.ts
index 1c6b6089..206a4db8 100644
--- a/src/scenes/mainScene.ts
+++ b/src/scenes/mainScene.ts
@@ -1,28 +1,117 @@
-import Phaser from "phaser";
-import PhaserLogo from "../objects/phaserLogo";
-import FpsText from "../objects/fpsText";
-
-export default class MainScene extends Phaser.Scene {
- fpsText: FpsText;
+import PauseMenu from "../objects/PauseMenu";
+import Background from "../objects/Background";
+import SFX from "../objects/SFX";
+import Game from "../objects/Game";
+import baseScene from "./baseScene";
+export default class MainScene extends baseScene {
constructor() {
- super({ key: "MainScene" });
+ super({ key: "MainGameScene" });
+ this.sfx = SFX.getInstance(this);
+ }
+
+ // receive data from the progressionscene and store it in the gameData property
+ init(data: Game) {
+ this.gameData = data;
}
create() {
- new PhaserLogo(this, this.cameras.main.width / 2, 0);
- this.fpsText = new FpsText(this);
+ const backgroundImage = Background.getInstance(this, "background");
+ backgroundImage.create();
+ this.gameData.resetObjectives();
- const message = `Phaser v${Phaser.VERSION}`;
- this.add
- .text(this.cameras.main.width - 15, 15, message, {
- color: "#000000",
+ this.gameBoard = this.gameData.createBoard(this);
+
+ this.anyValidSolutions = this.gameBoard.isPossibleSolution();
+
+ this.gameData.addRandomComplexObjectives(this.gameData.objectivesNum);
+
+ //SAMPLE OBJECTIVE
+ // this.gameData.addObjective(
+ // new Objective("One AND tile", 4, (tileTypes: string[]) => {
+ // const andTiles = tileTypes.filter(
+ // (tileType) => tileType === "andTile"
+ // );
+ // return andTiles.length >= 1;
+ // })
+ // );
+
+ this.score = 0;
+
+ this.gameData.handleObjectives(this, this.gameData.objectives);
+
+ console.log(this.gameData);
+ this.timerValue = this.gameData.timeLimitSeconds;
+ this.livesValue = this.gameData.numLives;
+ this.swapsValue = this.gameData.numInitialSwaps;
+
+ this.restartText = this.add.text(
+ 950,
+ 400,
+ `No more possible solutions,\npress Enter to restart.`,
+ {
fontSize: "24px",
- })
- .setOrigin(1, 0);
+ fontFamily: "Arial",
+ color: "#000000",
+ }
+ );
+ this.restartText.visible = !this.anyValidSolutions;
+
+ //console.log("Hello");
+
+ this.pauseMenu = new PauseMenu(this);
+ this.pauseMenu.setVisible(false);
+
+ // Add input event listeners
+ this.input.keyboard?.on("keydown", this.handleKeydown, this);
+ this.scoreText = this.add.text(
+ (this.game.config.width as number) / 2 - 15,
+ 10,
+ "Score: 0",
+ {
+ fontSize: "32px",
+ color: "#000",
+ }
+ );
+ this.scoreText.setOrigin(0.5, 0);
+ this.timerText = this.add.text(10, 10, `Time: ${this.timerValue}`, {
+ fontSize: "32px",
+ color: "#000",
+ });
+ this.time.addEvent({
+ delay: 1000, // 1 second
+ callback: this.updateTimer,
+ callbackScope: this,
+ loop: true,
+ });
+ this.livesText = this.add.text(10, 50, `Lives: ${this.livesValue}`, {
+ fontSize: "32px",
+ color: "#000",
+ });
+ this.swapsText = this.add.text(10, 90, `Swaps: ${this.swapsValue}`, {
+ fontSize: "32px",
+ color: "#000",
+ });
+ }
+ private updateTimer() {
+ if (this.pauseMenu?.visible) {
+ return; //dont decrement timer if paused
+ }
+ this.timerValue--;
+ this.timerText.setText(`Time: ${this.timerValue}`);
+
+ // Handle timer expiration (if needed)
+ if (this.timerValue === 0) {
+ this.endGame("You ran out of time!");
+ }
}
update() {
- this.fpsText.update();
+ if (this.livesValue == 0) {
+ this.endGame("You lost all of your lives!");
+ }
+ if (this.swapsValue == 0) {
+ this.endGame("You ran out of swaps!");
+ }
}
}
diff --git a/src/scenes/menuScene.ts b/src/scenes/menuScene.ts
new file mode 100644
index 00000000..2e9a68d3
--- /dev/null
+++ b/src/scenes/menuScene.ts
@@ -0,0 +1,99 @@
+import Phaser from "phaser";
+import Background from "../objects/Background";
+import SFX from "../objects/SFX";
+import Button from "../objects/Button";
+import Sketch from "../objects/Sketch";
+
+export default class MenuScene extends Phaser.Scene {
+ private sfx: SFX;
+ private sfxCreated: boolean = false;
+
+ constructor() {
+ super({ key: "MenuScene" });
+ this.sfx = SFX.getInstance(this);
+ }
+
+ create() {
+ const backgroundImage = Background.getInstance(this, "background");
+ backgroundImage.create();
+
+ if (!this.sfxCreated) {
+ this.sfx.create();
+ this.sfxCreated = true;
+ }
+
+ // Add title text
+
+ const titleImg = this.add.image(
+ (this.game.config.width as number) * 0.5,
+ (this.game.config.height as number) * 0.25,
+ "title"
+ );
+ titleImg.setOrigin(0.5);
+ titleImg.setScale(1.2);
+
+ this.tweens.add({
+ targets: titleImg,
+ scale: 1.4,
+ duration: 1200, // Duration of the tween in milliseconds
+ yoyo: true, // Reverse the tween on completion
+ repeat: -1, // Repeat indefinitely
+ ease: "Sine.easeInOut", // Easing function for smooth animation
+ });
+
+ // Add Play button
+ const middleWidth = (this.game.config.width as number) * 0.5;
+ const middleHeight = (this.game.config.height as number) * 0.5;
+ const buttonSpacing = (this.game.config.height as number) * 0.1;
+ let index = 0;
+ /*const playButton = */ new Button(
+ this,
+ middleWidth,
+ middleHeight + buttonSpacing * index,
+ "Play",
+ () => {
+ this.scene.start("ProgressionScene");
+ }
+ );
+ index++;
+ /*const settingsButton = */ new Button(
+ this,
+ middleWidth,
+ middleHeight + buttonSpacing * index,
+ "Settings",
+ () => {
+ this.scene.start("SettingsScene");
+ }
+ );
+
+ index++;
+ /*const controlsButton = */ new Button(
+ this,
+ middleWidth,
+ middleHeight + buttonSpacing * index,
+ "Controls",
+ () => {
+ this.scene.start("ControlScene");
+ }
+ );
+
+ index++;
+ /*const creditsButton = */ new Button(
+ this,
+ middleWidth,
+ middleHeight + buttonSpacing * index,
+ "Credits",
+ () => {
+ this.scene.start("CreditsScene");
+ }
+ );
+
+ const maxWidth = this.game.config.width as number;
+ const maxHeight = this.game.config.height as number;
+
+ new Sketch(this, 150, 150, 120);
+ new Sketch(this, maxWidth - 150, 200, 120);
+ new Sketch(this, maxWidth - 150, maxHeight - 150, 120);
+ new Sketch(this, 100, maxHeight - 150, 120);
+ }
+}
diff --git a/src/scenes/preloadScene.ts b/src/scenes/preloadScene.ts
index c17b81ba..71bf3c31 100644
--- a/src/scenes/preloadScene.ts
+++ b/src/scenes/preloadScene.ts
@@ -1,15 +1,73 @@
import Phaser from "phaser";
+import SFX from "../objects/SFX";
+import Sketch from "../objects/Sketch";
+import victorySketch from "../objects/victorySketch";
export default class PreloadScene extends Phaser.Scene {
+ private sfx: SFX;
+
constructor() {
super({ key: "PreloadScene" });
+ this.sfx = SFX.getInstance(this);
}
preload() {
- this.load.image("phaser-logo", "assets/img/phaser-logo.png");
+ //consider having a preload function in the tile class to handle this like SFX does
+ this.load.image("trueTile", "assets/tiles/trueTile.png");
+ this.load.image("falseTile", "assets/tiles/falseTile.png");
+ this.load.image("andTile", "assets/tiles/andTile.png");
+ this.load.image("orTile", "assets/tiles/orTile.png");
+ this.load.image("leftParenTile", "assets/tiles/leftParenTile.png");
+ this.load.image("rightParenTile", "assets/tiles/rightParenTile.png");
+ this.load.image("xorTile", "assets/tiles/xorTile.png");
+ this.load.image("notTile", "assets/tiles/notTile.png");
+
+ this.load.image(
+ "leftParenTileSelect",
+ "assets/tiles/leftParenTileSelect.png"
+ );
+ this.load.image(
+ "rightParenTileSelect",
+ "assets/tiles/rightParenTileSelect.png"
+ );
+ this.load.image("notTileSelect", "assets/tiles/notTileSelect.png");
+ this.load.image("xorTileSelect", "assets/tiles/xorTileSelect.png");
+ this.load.image("orTileSelect", "assets/tiles/orTileSelect.png");
+ this.load.image("andTileSelect", "assets/tiles/andTileSelect.png");
+ this.load.image("trueTileSelect", "assets/tiles/trueTileSelect.png");
+ this.load.image("falseTileSelect", "assets/tiles/falseTileSelect.png");
+
+ this.load.image("background", "assets/img/looseleaf.jpeg");
+ this.load.image("title", "assets/img/title-screen-boolean-bonanza.png");
+
+ this.load.image(
+ "sliderHandle",
+ "assets/img/soundSprites/sliderHandle.png"
+ );
+ this.load.image("soundMute", "assets/img/soundSprites/soundMute.png");
+ this.load.image("soundLow", "assets/img/soundSprites/soundLow.png");
+ this.load.image(
+ "soundMedium",
+ "assets/img/soundSprites/soundMedium.png"
+ );
+ this.load.image("soundHigh", "assets/img/soundSprites/soundHigh.png");
+
+ this.load.audio("backgroundMusic", "assets/music/bg-music-1.wav");
+
+ this.load.image("gameOver", "assets/img/game-over.png");
+ this.load.image(
+ "crumbleBackground",
+ "assets/img/crumbled-paper-background.png"
+ );
+ this.load.image("fail", "assets/img/fail.png");
+ this.load.image("complete", "assets/img/level-complete.png");
+
+ this.sfx.preload();
+ Sketch.preload(this);
+ victorySketch.preload(this);
}
create() {
- this.scene.start("MainScene");
+ this.scene.start("MenuScene");
}
}
diff --git a/src/scenes/progressionScene.ts b/src/scenes/progressionScene.ts
new file mode 100644
index 00000000..7ce1881c
--- /dev/null
+++ b/src/scenes/progressionScene.ts
@@ -0,0 +1,512 @@
+import Phaser from "phaser";
+import Game from "../objects/Game";
+import Stage from "../objects/Stages";
+import Background from "../objects/Background";
+import SFX from "../objects/SFX";
+import Button from "../objects/Button";
+import Sketch from "../objects/Sketch";
+
+function createText(
+ scene: Phaser.Scene,
+ x: number,
+ y: number,
+ text: string,
+ style: Phaser.Types.GameObjects.Text.TextStyle
+) {
+ const textObj = scene.add.text(x, y, text, style);
+ textObj.setOrigin(0.5);
+ return textObj;
+}
+
+function createButton(
+ scene: Phaser.Scene,
+ x: number,
+ y: number,
+ label: string,
+ callback: () => void,
+ fontSize: string = "24px"
+) {
+ return new Button(scene, x, y, label, callback, fontSize)
+ .setOrigin(0.5)
+ .setDepth(1);
+}
+
+function createNavigationButtons(
+ scene: Phaser.Scene,
+ screenWidth: number,
+ screenHeight: number,
+ changeStage: (direction: number) => void
+) {
+ createButton(
+ scene,
+ screenWidth * 0.1,
+ screenHeight * 0.5,
+ "<",
+ () => {
+ changeStage(-1);
+ },
+ "48px"
+ );
+ createButton(
+ scene,
+ screenWidth * 0.9,
+ screenHeight * 0.5,
+ ">",
+ () => {
+ changeStage(1);
+ },
+ "48px"
+ );
+}
+
+function createStageTitle(
+ scene: Phaser.Scene,
+ screenWidth: number,
+ screenHeight: number,
+ stages: Stage[],
+ currentStageIndex: number
+): Phaser.GameObjects.Text {
+ return createText(
+ scene,
+ screenWidth * 0.5,
+ screenHeight * 0.2,
+ stages[currentStageIndex].name,
+ {
+ fontSize: "48px",
+ fontFamily: "Arial",
+ color: "#000000",
+ }
+ );
+}
+
+function createBackButton(scene: Phaser.Scene) {
+ createButton(
+ scene,
+ 70,
+ 70,
+ "Back",
+ () => scene.scene.start("MenuScene"),
+ "32px"
+ );
+}
+
+function createFreeplayButton(
+ scene: Phaser.Scene,
+ screenWidth: number,
+ screenHeight: number,
+ openFreeplaySettings: () => void
+) {
+ createButton(
+ scene,
+ screenWidth * 0.5,
+ screenHeight * 0.8,
+ "Freeplay",
+ openFreeplaySettings,
+ "32px"
+ );
+}
+
+function createGameButtons(
+ scene: Phaser.Scene,
+ games: Game[],
+ startX: number,
+ gameListY: number,
+ buttonSize: number,
+ gameSpacing: number,
+ sfx: SFX,
+ stageTitleText: Phaser.GameObjects.Text,
+ stages: Stage[],
+ currentStageIndex: number
+) {
+ const gameButtonsShown: Phaser.GameObjects.Container[] = [];
+
+ games.forEach((game, index) => {
+ const isLocked = game.isLocked;
+
+ const gameContainer = scene.add.container(
+ startX + index * (buttonSize + gameSpacing),
+ gameListY
+ );
+
+ const gameButton = scene.add.text(
+ 0,
+ -buttonSize / 3,
+ game.name + `\n` + (isLocked ? " (Locked)" : ""),
+ {
+ fontSize: "24px",
+ fontFamily: "Arial",
+ color: isLocked ? "#888888" : "#ffffff",
+ backgroundColor: "#4e342e",
+ padding: { x: 10, y: 10 },
+ fixedWidth: buttonSize,
+ fixedHeight: buttonSize + 30,
+ align: "center",
+ }
+ );
+ gameButton.setOrigin(0.5);
+
+ const boardSizeText = scene.add.text(
+ 0,
+ -60,
+ `Board: ${game.boardSize}x${game.boardSize}`,
+ {
+ fontSize: "18px",
+ fontFamily: "Arial",
+ color: "#ffffff",
+ align: "center",
+ }
+ );
+ boardSizeText.setOrigin(0.5);
+
+ const tilesContainer = scene.add.container(0, buttonSize / 6);
+ const tileSprites = game.tileTypes;
+ const maxTilesPerRow = 4;
+
+ tileSprites.forEach((sprite, i) => {
+ const tileButton = scene.add.sprite(
+ ((i % maxTilesPerRow) - (maxTilesPerRow - 1) / 2) * 30,
+ Math.floor(i / maxTilesPerRow) * 30 - 45,
+ sprite
+ );
+ tileButton.setScale(0.25);
+ tilesContainer.add(tileButton);
+ });
+
+ gameContainer.add([gameButton, boardSizeText, tilesContainer]);
+
+ gameContainer.setSize(buttonSize, buttonSize);
+ gameContainer.setInteractive(
+ new Phaser.Geom.Rectangle(0, 0, buttonSize, buttonSize),
+ Phaser.Geom.Rectangle.Contains
+ );
+ gameContainer.setInteractive({ useHandCursor: !isLocked });
+
+ if (!isLocked) {
+ gameContainer.on("pointerover", () => {
+ gameButton.setStyle({ backgroundColor: "#6e4f3e" });
+ gameContainer.setScale(1.1);
+ });
+
+ gameContainer.on("pointerout", () => {
+ gameButton.setStyle({ backgroundColor: "#4e342e" });
+ gameContainer.setScale(1);
+ });
+
+ gameContainer.on("pointerdown", () => {
+ sfx.play("crumple-paper-1");
+ console.log(stages);
+ scene.registry.set(
+ "currentStage",
+ stages[currentStageIndex].name
+ );
+ scene.registry.set("currentGame", game.name);
+
+ if (
+ stageTitleText.text === "Beginner" &&
+ game.name === "Tutorial"
+ ) {
+ game.startTutorial(scene);
+ } else {
+ game.startGame(scene);
+ }
+ });
+ }
+
+ gameButtonsShown.push(gameContainer);
+ });
+
+ return gameButtonsShown;
+}
+
+export default class ProgressionScene extends Phaser.Scene {
+ private currentStageIndex: number = 0;
+ private stages: Stage[];
+ private stageTitleText: Phaser.GameObjects.Text;
+ private sfx: SFX;
+ private gameButtonsShown: Phaser.GameObjects.Container[] = [];
+ private tileButtonsShown: Phaser.GameObjects.Sprite[] = [];
+
+ constructor() {
+ super({ key: "ProgressionScene" });
+ this.sfx = SFX.getInstance(this);
+ }
+
+ create() {
+ this.stages = this.createStages();
+ const backgroundImage = Background.getInstance(this, "background");
+ backgroundImage.create();
+ const screenWidth = this.game.config.width as number;
+ const screenHeight = this.game.config.height as number;
+ this.registry.set("stages", this.stages);
+
+ this.stageTitleText = createStageTitle(
+ this,
+ screenWidth,
+ screenHeight,
+ this.stages,
+ this.currentStageIndex
+ );
+ createNavigationButtons(
+ this,
+ screenWidth,
+ screenHeight,
+ this.changeStage.bind(this)
+ );
+ this.showGames(this.stages[this.currentStageIndex].games);
+ createBackButton(this);
+ createFreeplayButton(
+ this,
+ screenWidth,
+ screenHeight,
+ this.openFreeplaySettings.bind(this)
+ );
+ const maxWidth = this.game.config.width as number;
+ const maxHeight = this.game.config.height as number;
+
+ new Sketch(this, 150, 150, 120);
+ new Sketch(this, maxWidth - 150, 120, 120);
+ new Sketch(this, maxWidth - 150, maxHeight - 120, 120);
+ new Sketch(this, 100, maxHeight - 150, 120);
+ }
+
+ changeStage(direction: number) {
+ this.currentStageIndex =
+ (this.currentStageIndex + direction + this.stages.length) %
+ this.stages.length;
+ this.stageTitleText.setText(this.stages[this.currentStageIndex].name);
+ this.showGames(this.stages[this.currentStageIndex].games);
+ this.sfx.play("pop-click-1");
+ }
+
+ openFreeplaySettings() {
+ this.scene.start("FreeplayScene");
+ }
+
+ hideGames() {
+ this.gameButtonsShown.forEach((button) => {
+ button.destroy();
+ });
+ this.gameButtonsShown = [];
+ this.tileButtonsShown.forEach((button) => {
+ button.destroy();
+ });
+ this.tileButtonsShown = [];
+ }
+
+ showGames(games: Game[]) {
+ this.hideGames();
+
+ const screenWidth = this.game.config.width as number;
+ const screenHeight = this.game.config.height as number;
+ const gameListY = screenHeight * 0.55;
+ const gameSpacing = 20;
+ const buttonSize = Math.min(
+ screenWidth / Math.min(games.length, 5),
+ 165
+ );
+ const startX =
+ (screenWidth - games.length * (buttonSize + gameSpacing)) / 2 + 90;
+
+ this.gameButtonsShown = createGameButtons(
+ this,
+ games,
+ startX,
+ gameListY,
+ buttonSize,
+ gameSpacing,
+ this.sfx,
+ this.stageTitleText,
+ this.stages,
+ this.currentStageIndex
+ );
+ }
+
+ createStages(): Stage[] {
+ let currentLevel = this.registry.get("level") || 0;
+ console.log("level: ", currentLevel);
+ //currentLevel = 0;
+ let levelCount = 0;
+ return [
+ new Stage("Beginner", [
+ new Game(
+ levelCount,
+ "Tutorial",
+ ["trueTile", "falseTile", "orTile"],
+ 3,
+ 180,
+ 99,
+ 99,
+ 1,
+ currentLevel < levelCount++
+ ),
+ new Game(
+ levelCount,
+ "Game 1",
+ ["trueTile", "andTile"],
+ 3,
+ 100,
+ 3,
+ 99,
+ 1,
+ currentLevel < levelCount++
+ ),
+ new Game(
+ levelCount,
+ "Game 2",
+ ["trueTile", "falseTile", "orTile", "andTile"],
+ 3,
+ 100,
+ 3,
+ 99,
+ 2,
+ currentLevel < levelCount++
+ ),
+ new Game(
+ levelCount,
+ "Game 3",
+ ["trueTile", "falseTile", "andTile", "orTile", "notTile"],
+ 4,
+ 100,
+ 3,
+ 99,
+ 2,
+ currentLevel < levelCount++
+ ),
+ ]),
+ new Stage("Intermediate", [
+ new Game(
+ levelCount,
+ "Game 1",
+ ["trueTile", "falseTile", "andTile", "orTile"],
+ 5,
+ 120,
+ 2,
+ 20,
+ 3,
+ currentLevel < levelCount++
+ ),
+ new Game(
+ levelCount,
+ "Game 2",
+ ["trueTile", "falseTile", "andTile", "orTile"],
+ 7,
+ 120,
+ 2,
+ 20,
+ 3,
+ currentLevel < levelCount++
+ ),
+ new Game(
+ levelCount,
+ "Game 3",
+ ["trueTile", "falseTile", "orTile", "andTile", "notTile"],
+ 6,
+ 120,
+ 2,
+ 20,
+ 3,
+ currentLevel < levelCount++
+ ),
+ new Game(
+ levelCount,
+ "Game 4",
+ ["trueTile", "falseTile", "andTile", "orTile", "xorTile"],
+ 5,
+ 120,
+ 2,
+ 20,
+ 3,
+ currentLevel < levelCount++
+ ),
+ new Game(
+ levelCount,
+ "Game 5",
+ [
+ "trueTile",
+ "falseTile",
+ "andTile",
+ "orTile",
+ "xorTile",
+ "notTile",
+ ],
+ 6,
+ 120,
+ 2,
+ 20,
+ 3,
+ currentLevel < levelCount++
+ ),
+ ]),
+ new Stage("Advanced", [
+ new Game(
+ levelCount,
+ "Game 1",
+ ["trueTile", "falseTile", "orTile", "andTile", "xorTile"],
+ 7,
+ 120,
+ 1,
+ 30,
+ 3,
+ currentLevel < levelCount++
+ ),
+ new Game(
+ levelCount,
+ "Game 2",
+ [
+ "trueTile",
+ "falseTile",
+ "orTile",
+ "andTile",
+ "xorTile",
+ "notTile",
+ ],
+ 8,
+ 120,
+ 1,
+ 30,
+ 3,
+ currentLevel < levelCount++
+ ),
+ new Game(
+ levelCount,
+ "Game 3",
+ [
+ "trueTile",
+ "falseTile",
+ "orTile",
+ "andTile",
+ "notTile",
+ "leftParenTile",
+ "rightParenTile",
+ ],
+ 9,
+ 120,
+ 1,
+ 30,
+ 3,
+ currentLevel < levelCount++
+ ),
+ new Game(
+ levelCount,
+ "Game 4",
+ [
+ "trueTile",
+ "falseTile",
+ "orTile",
+ "andTile",
+ "xorTile",
+ "notTile",
+ "leftParenTile",
+ "rightParenTile",
+ ],
+ 9,
+ 120,
+ 1,
+ 30,
+ 3,
+ currentLevel < levelCount++
+ ),
+ ]),
+ ];
+ }
+}
diff --git a/src/scenes/settingsScene.ts b/src/scenes/settingsScene.ts
new file mode 100644
index 00000000..71c05a8b
--- /dev/null
+++ b/src/scenes/settingsScene.ts
@@ -0,0 +1,218 @@
+import Phaser from "phaser";
+import Background from "../objects/Background";
+import SFX from "../objects/SFX";
+import Button from "../objects/Button";
+import Sketch from "../objects/Sketch";
+
+export default class SettingsScene extends Phaser.Scene {
+ private sfx: SFX;
+ private music: Phaser.Sound.HTML5AudioSound;
+ private sfxSounds: Map;
+
+ constructor() {
+ super({ key: "SettingsScene" });
+ this.sfx = SFX.getInstance(this);
+ }
+
+ create() {
+ const sliderMinX = 600;
+ const sliderStartX = 400;
+ const sliderMaxX = sliderMinX + sliderStartX;
+ const sliderY = (this.game.config.height as number) * 0.3;
+ const backgroundImage = Background.getInstance(this, "background");
+
+ backgroundImage.create();
+
+ this.music = this.sfx.getMusic();
+ this.sfxSounds = this.sfx.getSFX();
+
+ // get current volume
+ const musicVolume = this.music.volume;
+ const sfxVolume = this.sfxSounds.get("pop-click-1")?.volume;
+
+ // create volume sprites
+ const musicVolumeSliders = this.createVolumeSprites(
+ sliderMaxX,
+ sliderY,
+ 0
+ );
+ const SFXVolumeSliders = this.createVolumeSprites(
+ sliderMaxX,
+ sliderY,
+ 50
+ );
+
+ this.volumeSetVisible(musicVolumeSliders, musicVolume);
+ this.volumeSetVisible(SFXVolumeSliders, sfxVolume || 0);
+
+ const volumeText = this.add.text(
+ 350,
+ 200,
+ `Music: ${(musicVolume * 100).toFixed(0)}%`,
+ {
+ fontSize: "32px",
+ fontFamily: "Arial",
+ align: "center",
+ color: "#000",
+ }
+ );
+
+ const SFXText = this.add.text(
+ 350,
+ 250,
+ `SFX: ${(sfxVolume ? sfxVolume * 100 : 0).toFixed(0)}%`,
+ {
+ fontSize: "32px",
+ fontFamily: "Arial",
+ align: "center",
+ color: "#000",
+ }
+ );
+
+ const settingsText = this.add.text(
+ (this.game.config.width as number) * 0.5,
+ (this.game.config.height as number) * 0.2,
+ "Settings\n\n",
+ {
+ fontSize: "32px",
+ fontFamily: "Arial",
+ color: "#000000",
+ align: "center",
+ }
+ );
+ settingsText.setOrigin(0.5);
+
+ // create slider bars
+ const sliderBarMusic = this.add.graphics();
+ sliderBarMusic.fillStyle(0x000000, 1);
+ sliderBarMusic.fillRect(sliderMinX, sliderY, sliderStartX, 5);
+
+ const sliderBarSFX = this.add.graphics();
+ sliderBarSFX.fillStyle(0x000000, 1);
+ sliderBarSFX.fillRect(sliderMinX, sliderY + 50, sliderStartX, 5);
+
+ // create slider handles
+ const sliderHandleMusic = this.add
+ .sprite(sliderMinX, sliderY, "sliderHandle")
+ .setInteractive();
+ sliderHandleMusic.setInteractive();
+
+ const sliderHandleSFX = this.add
+ .sprite(sliderMinX, sliderY + 50, "sliderHandle")
+ .setInteractive();
+ sliderHandleSFX.setInteractive();
+
+ // set the initial position of the slider handles
+ const soundVolumeMusic = this.music.volume;
+ sliderHandleMusic.x = sliderMinX + soundVolumeMusic * sliderStartX;
+
+ const soundVolumeSFX = this.sfxSounds.get("pop-click-1")?.volume;
+ sliderHandleSFX.x = sliderMinX + (soundVolumeSFX || 0) * sliderStartX;
+
+ this.input.setDraggable(sliderHandleMusic);
+ this.input.setDraggable(sliderHandleSFX);
+
+ this.input.on(
+ "drag",
+ (
+ pointer: Phaser.Input.Pointer,
+ gameObject: Phaser.GameObjects.GameObject,
+ dragX: number
+ ) => {
+ if (gameObject === sliderHandleMusic) {
+ if (dragX >= sliderMinX && dragX <= sliderMaxX) {
+ (gameObject as Phaser.GameObjects.Image).x = dragX;
+
+ // Update the volume
+ const volume = (dragX - sliderMinX) / sliderStartX;
+ this.music.volume = volume;
+
+ if (volume < 0.01) {
+ this.music.volume = 0;
+ }
+
+ volumeText.setText(
+ `Music: ${(volume * 100).toFixed(0)}%`
+ );
+ this.volumeSetVisible(musicVolumeSliders, volume);
+ }
+ } else if (gameObject === sliderHandleSFX) {
+ if (dragX >= sliderMinX && dragX <= sliderMaxX) {
+ (gameObject as Phaser.GameObjects.Image).x = dragX;
+
+ // Update the volume
+ const volume = (dragX - sliderMinX) / sliderStartX;
+ this.sfxSounds.forEach((sfx) => {
+ sfx.volume = volume;
+ });
+
+ if (volume < 0.01) {
+ this.sfxSounds.forEach((sfx) => {
+ sfx.volume = 0;
+ });
+ }
+
+ SFXText.setText(`SFX: ${(volume * 100).toFixed(0)}%`);
+ this.volumeSetVisible(SFXVolumeSliders, volume);
+ this.sfx.play("pop-click-1");
+ }
+ }
+ }
+ );
+
+ const backButton = new Button(
+ this,
+ 70,
+ 70,
+ "Back",
+ () => {
+ this.scene.start("MenuScene");
+ },
+ "24px"
+ );
+ backButton.setDepth(1);
+
+ const maxWidth = this.game.config.width as number;
+ const maxHeight = this.game.config.height as number;
+
+ new Sketch(this, 150, 150, 120);
+ new Sketch(this, maxWidth - 150, 120, 120);
+ new Sketch(this, maxWidth - 150, maxHeight - 120, 120);
+ new Sketch(this, 100, maxHeight - 150, 120);
+ }
+
+ // Create the volume sprite
+ createVolumeSprites(
+ sliderMaxX: number,
+ sliderY: number,
+ spriteYOffset: number
+ ) {
+ const spriteNames = [
+ "soundMute",
+ "soundLow",
+ "soundMedium",
+ "soundHigh",
+ ];
+ const volumeSprites: { [key: string]: Phaser.GameObjects.Sprite } = {};
+
+ spriteNames.forEach((volumeName) => {
+ volumeSprites[volumeName] = this.add
+ .sprite(sliderMaxX + 50, sliderY + spriteYOffset, volumeName)
+ .setScale(0.4)
+ .setVisible(false);
+ });
+
+ return volumeSprites;
+ }
+
+ // Set the volume sprites visible based on the volume
+ volumeSetVisible(
+ volumeSprites: { [key: string]: Phaser.GameObjects.Sprite },
+ volume: number
+ ) {
+ volumeSprites.soundMute.setVisible(volume < 0.01);
+ volumeSprites.soundLow.setVisible(volume > 0.01 && volume <= 0.33);
+ volumeSprites.soundMedium.setVisible(volume > 0.33 && volume <= 0.66);
+ volumeSprites.soundHigh.setVisible(volume > 0.66);
+ }
+}
diff --git a/src/scenes/tutorialScene.ts b/src/scenes/tutorialScene.ts
new file mode 100644
index 00000000..220b5cf1
--- /dev/null
+++ b/src/scenes/tutorialScene.ts
@@ -0,0 +1,317 @@
+import PauseMenu from "../objects/PauseMenu";
+import Background from "../objects/Background";
+import SFX from "../objects/SFX";
+import Game from "../objects/Game";
+import baseScene from "./baseScene";
+import Phaser from "phaser";
+
+export default class TutorialScene extends baseScene {
+ private instructionText: Phaser.GameObjects.Text;
+ private instructionString: string;
+ private counter = 0;
+ private tutorialStep: number;
+
+ constructor() {
+ super({ key: "TutorialScene" });
+ this.sfx = SFX.getInstance(this);
+ }
+
+ // receive data from the progressionscene and store it in the gameData property
+ init(data: Game) {
+ this.gameData = data;
+ }
+
+ create() {
+ //console.log(this.data.getAll());
+ this.tutorialStep = 0;
+ const backgroundImage = Background.getInstance(this, "background");
+ backgroundImage.create();
+ this.gameData.resetObjectives();
+
+ this.gameBoard = this.gameData.createBoard(this);
+
+ this.anyValidSolutions = this.gameBoard.isPossibleSolution();
+
+ this.gameData.addRandomComplexObjectives(
+ this.gameData.objectivesNum,
+ 1
+ );
+
+ this.score = 0;
+
+ this.gameData.handleObjectives(this, this.gameData.objectives);
+
+ console.log(this.gameData);
+ this.timerValue = this.gameData.timeLimitSeconds;
+ this.livesValue = this.gameData.numLives;
+ this.swapsValue = this.gameData.numInitialSwaps;
+
+ this.restartText = this.add.text(
+ 950,
+ 400,
+ `No more possible solutions,\npress Enter to restart.`,
+ {
+ fontSize: "24px",
+ fontFamily: "Arial",
+ color: "#000000",
+ }
+ );
+ this.restartText.visible = !this.anyValidSolutions;
+
+ this.pauseMenu = new PauseMenu(this);
+ this.pauseMenu.setVisible(false);
+
+ this.input.keyboard?.on("keydown", this.handleKeydown, this);
+ this.scoreText = this.add.text(
+ (this.game.config.width as number) / 2 - 15,
+ 10,
+ "Score: 0",
+ {
+ fontSize: "32px",
+ color: "#000",
+ }
+ );
+ this.scoreText.setOrigin(0.5, 0);
+ this.timerText = this.add.text(10, 10, `Time: ${this.timerValue}`, {
+ fontSize: "32px",
+ color: "#000",
+ });
+ this.time.addEvent({
+ delay: 1000, // 1 second
+ callback: this.updateTimer,
+ callbackScope: this,
+ loop: true,
+ });
+ this.livesText = this.add.text(10, 50, `Lives: ${this.livesValue}`, {
+ fontSize: "32px",
+ color: "#000",
+ });
+ this.swapsText = this.add.text(10, 90, `Swaps: ${this.swapsValue}`, {
+ fontSize: "32px",
+ color: "#000",
+ });
+
+ this.instructionString = "Click on a tile to select it";
+ this.instructionText = this.add.text(
+ (this.game.config.width as number) - 325,
+ 100,
+ "",
+ {
+ lineSpacing: -15,
+ fontFamily: "Architects Daughter",
+ fontStyle: "bold",
+ fontSize: "50px",
+ color: "#ff0000",
+ wordWrap: {
+ width: 300,
+ },
+ }
+ );
+
+ this.counter = 0;
+ this.time.addEvent({
+ delay: 50,
+ callback: () => {
+ this.instructionText.text +=
+ this.instructionString[this.counter];
+ this.counter++;
+ },
+ callbackScope: this,
+ repeat: this.instructionString.length - 1,
+ });
+
+ this.input.on("pointerdown", async () => {
+ if (this.tutorialStep === 0 && this.gameBoard.isTileSelected()) {
+ this.changeTextGreen();
+ this.tutorialStep++;
+
+ await new Promise((resolve) => {
+ let checkInterval = setInterval(() => {
+ if (this.counter >= this.instructionString.length) {
+ clearInterval(checkInterval);
+ resolve(null);
+ }
+ }, 100);
+ });
+ this.tutorialStep++;
+ if (this.counter === this.instructionString.length) {
+ this.instructionString =
+ "Use the arrow or WASD keys to move the selected tile";
+
+ this.instructionText.text = "";
+ this.changeTextRed();
+ this.buildTutorialText();
+ this.counter = 0;
+ }
+ }
+ });
+
+ if (this.input.keyboard) {
+ this.input.keyboard.on(
+ "keydown",
+ async (event: Phaser.Input.Keyboard.Key) => {
+ if (
+ this.tutorialStep === 2 &&
+ this.gameBoard.isTileSelected() &&
+ (event.keyCode === Phaser.Input.Keyboard.KeyCodes.UP ||
+ event.keyCode ===
+ Phaser.Input.Keyboard.KeyCodes.DOWN ||
+ event.keyCode ===
+ Phaser.Input.Keyboard.KeyCodes.LEFT ||
+ event.keyCode ===
+ Phaser.Input.Keyboard.KeyCodes.RIGHT ||
+ event.keyCode ===
+ Phaser.Input.Keyboard.KeyCodes.W ||
+ event.keyCode ===
+ Phaser.Input.Keyboard.KeyCodes.A ||
+ event.keyCode ===
+ Phaser.Input.Keyboard.KeyCodes.S ||
+ event.keyCode === Phaser.Input.Keyboard.KeyCodes.D)
+ ) {
+ this.changeTextGreen();
+ this.tutorialStep++;
+
+ await new Promise((resolve) => {
+ let checkInterval = setInterval(() => {
+ if (
+ this.counter >=
+ this.instructionString.length
+ ) {
+ clearInterval(checkInterval);
+ resolve(null);
+ }
+ }, 100);
+ });
+ this.tutorialStep++;
+ if (this.counter === this.instructionString.length) {
+ this.instructionString =
+ "Press 'r' or 'c' to select a row or column";
+ this.instructionText.text = "";
+ this.changeTextRed();
+ this.buildTutorialText();
+ this.counter = 0;
+ }
+ }
+ }
+ );
+ }
+
+ if (this.input.keyboard) {
+ this.input.keyboard.on(
+ "keydown",
+ async (event: Phaser.Input.Keyboard.Key) => {
+ if (
+ this.tutorialStep === 4 &&
+ this.gameBoard.isTileSelected() &&
+ (event.keyCode === Phaser.Input.Keyboard.KeyCodes.R ||
+ event.keyCode === Phaser.Input.Keyboard.KeyCodes.C)
+ ) {
+ this.changeTextGreen();
+ this.tutorialStep++;
+
+ await new Promise((resolve) => {
+ let checkInterval = setInterval(() => {
+ if (
+ this.counter >=
+ this.instructionString.length
+ ) {
+ clearInterval(checkInterval);
+ resolve(null);
+ }
+ }, 100);
+ });
+ this.tutorialStep++;
+ if (this.counter === this.instructionString.length) {
+ this.instructionString =
+ "Press enter to confirm your choice";
+ this.instructionText.text = "";
+ this.changeTextRed();
+ this.buildTutorialText();
+ this.counter = 0;
+ }
+ }
+ }
+ );
+ }
+
+ if (this.input.keyboard) {
+ this.input.keyboard.on(
+ "keydown",
+ async (event: Phaser.Input.Keyboard.Key) => {
+ if (
+ this.tutorialStep === 6 &&
+ this.gameBoard.isTileSelected() &&
+ event.keyCode === Phaser.Input.Keyboard.KeyCodes.ENTER
+ ) {
+ this.changeTextGreen();
+ this.tutorialStep++;
+
+ await new Promise((resolve) => {
+ let checkInterval = setInterval(() => {
+ if (
+ this.counter >=
+ this.instructionString.length
+ ) {
+ clearInterval(checkInterval);
+ resolve(null);
+ }
+ }, 100);
+ });
+ this.tutorialStep++;
+ if (this.counter === this.instructionString.length) {
+ this.instructionString =
+ "Evaluate the row/column to true to complete the objectives";
+ this.instructionText.text = "";
+ this.changeTextRed();
+ this.buildTutorialText();
+ this.counter = 0;
+ }
+ }
+ }
+ );
+ }
+ }
+
+ private updateTimer() {
+ if (this.pauseMenu?.visible) {
+ return; //dont decrement timer if paused
+ }
+ this.timerValue--;
+ this.timerText.setText(`Time: ${this.timerValue}`);
+
+ // Handle timer expiration (if needed)
+ if (this.timerValue === 0) {
+ this.endGame("You ran out of time!");
+ }
+ }
+
+ update() {
+ if (this.livesValue == 0) {
+ this.endGame("You lost all of your lives!");
+ }
+ if (this.swapsValue == 0) {
+ this.endGame("You ran out of swaps!");
+ }
+ }
+
+ private buildTutorialText() {
+ this.time.addEvent({
+ delay: 50,
+ callback: () => {
+ this.instructionText.text +=
+ this.instructionString[this.counter];
+ this.counter++;
+ },
+ callbackScope: this,
+ repeat: this.instructionString.length - 1,
+ });
+ }
+
+ private changeTextGreen() {
+ this.instructionText.setColor("#00ff00");
+ }
+
+ private changeTextRed() {
+ this.instructionText.setColor("#ff0000");
+ }
+}
diff --git a/src/types/DirectionType.ts b/src/types/DirectionType.ts
new file mode 100644
index 00000000..e525c73c
--- /dev/null
+++ b/src/types/DirectionType.ts
@@ -0,0 +1,5 @@
+export enum DirectionType {
+ ROW,
+ COL,
+ NONE,
+}
diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js
index 03a698e7..995c88b6 100644
--- a/webpack/webpack.common.js
+++ b/webpack/webpack.common.js
@@ -35,7 +35,7 @@ module.exports = {
},
plugins: [
new HtmlWebpackPlugin({
- gameName: "My Phaser Game",
+ gameName: "Boolean Bonanza!",
template: "assets/index.html",
}),
new CopyWebpackPlugin({