Skip to content
Commits on Source (16)
Greedy Brain 100 100 16419
gyuh 15 10 6103
kjhgj 15 10 6070
15 10 5869
15 10 2755
Foo 15 10 1938
Simple Brain 15 10 1781
hoooodnbjeijohgjg 15 10 1761
Ari 15 10 1314
15 10 1215
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="JavadocGenerationManager"> <component name="JavadocGenerationManager">
<option name="OUTPUT_DIRECTORY" value="$USER_HOME$/Desktop/JavaDoc" /> <option name="OUTPUT_DIRECTORY" value="$USER_HOME$/Desktop/JavaDoc" />
<option name="OPTION_DOCUMENT_TAG_USE" value="true" /> <option name="OPTION_DOCUMENT_TAG_USE" value="true" />
<option name="OPTION_DOCUMENT_TAG_AUTHOR" value="true" /> <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="true" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" /> <output url="file://$PROJECT_DIR$/classes" />
</component> </component>
</project> </project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="AnalysisUIOptions">
<option name="ANALYZE_INJECTED_CODE" value="false" />
</component>
<component name="ArtifactsWorkspaceSettings"> <component name="ArtifactsWorkspaceSettings">
<artifacts-to-build> <artifacts-to-build>
<artifact name="sge_dist:jar" /> <artifact name="sge_dist:jar" />
</artifacts-to-build> </artifacts-to-build>
</component> </component>
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="78671b20-6a1e-42c8-be28-50b7f38fa7ff" name="Default Changelist" comment=""> <list default="true" id="78671b20-6a1e-42c8-be28-50b7f38fa7ff" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/.idea/artifacts/sge_dist_jar.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/edu/bu/ec504/spr24/sameGameTris/SameGameTris.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/edu/bu/ec504/spr24/sameGameTris/SameGameTris.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/Brain/smarterBrain.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.highscores.db" beforeDir="false" afterPath="$PROJECT_DIR$/.highscores.db" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/Brain/Brain.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/Brain/Brain.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/Brain/lazyBrain.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/Brain/lazyBrain.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/Brain/simpleBrain.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/Brain/simpleBrain.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/highScores/highScore.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/highScores/highScore.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/sameGameTris/CircleColor.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/sameGameTris/CircleColor.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/sameGameTris/CircleRolloverEvent.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/sameGameTris/CircleRolloverEvent.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/sameGameTris/GUI.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/sameGameTris/GUI.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/sameGameTris/SelfAwareCircle.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/sameGameTris/SelfAwareCircle.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/sameGameTris/sge.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/edu/bu/ec504/spr19/sameGameTris/sge.java" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
...@@ -37,38 +30,69 @@ ...@@ -37,38 +30,69 @@
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component> </component>
<component name="MacroExpansionManager">
<option name="directoryName" value="iond1hng" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 0
}</component>
<component name="ProjectId" id="1WwNkHNN3P3eLWEr1fM1C55vWZG" /> <component name="ProjectId" id="1WwNkHNN3P3eLWEr1fM1C55vWZG" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState"> <component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showExcludedFiles" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent"> <component name="PropertiesComponent">{
<property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" /> &quot;keyToString&quot;: {
<property name="WebServerToolWindowFactoryState" value="false" /> &quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;,
<property name="aspect.path.notification.shown" value="true" /> &quot;Application.EC504 SameGameTris.executor&quot;: &quot;Run&quot;,
<property name="com.android.tools.idea.instantapp.provision.ProvisionBeforeRunTaskProvider.myTimeStamp" value="1548371024044" /> &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
<property name="project.structure.last.edited" value="Artifacts" /> &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
<property name="project.structure.proportion" value="0.15" /> &quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
<property name="project.structure.side.proportion" value="0.2" /> &quot;git-widget-placeholder&quot;: &quot;master&quot;,
<property name="run.code.analysis.last.selected.profile" value="pProject Default" /> &quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
<property name="settings.editor.selected.configurable" value="preferences.keymap" /> &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
</component> &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;project.structure.last.edited&quot;: &quot;Project&quot;,
&quot;project.structure.proportion&quot;: &quot;0.0&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.36091954&quot;,
&quot;run.code.analysis.last.selected.profile&quot;: &quot;pProject Default&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.lookFeel&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}</component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="MoveMembersDialog.RECENTS_KEY">
<recent name="edu.bu.ec504.spr24.Brain.LazyBrain.Board" />
</key>
<key name="MoveClassesOrPackagesDialog.RECENTS_KEY"> <key name="MoveClassesOrPackagesDialog.RECENTS_KEY">
<recent name="edu.bu.ec504.spr19" /> <recent name="edu.bu.ec504.spr19" />
<recent name="edu.bu.ec504.spr19.sameGameTris" /> <recent name="edu.bu.ec504.spr19.sameGameTris" />
</key> </key>
<key name="MoveInnerDialog.RECENTS_KEY">
<recent name="edu.bu.ec504.spr24.Brain" />
</key>
</component> </component>
<component name="RunManager"> <component name="RunManager">
<configuration name="EC504 SameGameTris" type="Application" factoryName="Application"> <configuration name="EC504 SameGameTris" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="edu.bu.ec504.spr19.sameGameTris.sge" /> <option name="MAIN_CLASS_NAME" value="edu.bu.ec504.spr24.sameGameTris.SameGameTris" />
<module name="sge_dist" /> <module name="sge_dist" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
</configuration> </configuration>
</component> </component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="jdk-17.0.10-corretto-17.0.10-f644763e9732-24582b8d" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="SvnConfiguration"> <component name="SvnConfiguration">
<configuration /> <configuration />
</component> </component>
...@@ -80,7 +104,14 @@ ...@@ -80,7 +104,14 @@
<option name="presentableId" value="Default" /> <option name="presentableId" value="Default" />
<updated>1548370618181</updated> <updated>1548370618181</updated>
<workItem from="1548370619328" duration="375000" /> <workItem from="1548370619328" duration="375000" />
<workItem from="1580056843261" duration="20889000" /> <workItem from="1580056843261" duration="23542000" />
<workItem from="1706041955741" duration="38000" />
<workItem from="1706042051986" duration="6820000" />
<workItem from="1706057188033" duration="218000" />
<workItem from="1706070762735" duration="2123000" />
<workItem from="1706112889389" duration="6798000" />
<workItem from="1706139932552" duration="8790000" />
<workItem from="1707268803363" duration="371000" />
</task> </task>
<task id="LOCAL-00001" summary="Apparently working version."> <task id="LOCAL-00001" summary="Apparently working version.">
<created>1580084914114</created> <created>1580084914114</created>
...@@ -89,119 +120,83 @@ ...@@ -89,119 +120,83 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1580084914114</updated> <updated>1580084914114</updated>
</task> </task>
<option name="localTasksCounter" value="2" /> <task id="LOCAL-00002" summary="Final version.">
<created>1580091112832</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1580091112832</updated>
</task>
<task id="LOCAL-00003" summary="Minor tweaks">
<created>1580097683638</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1580097683638</updated>
</task>
<task id="LOCAL-00004" summary="Cleaning up.">
<option name="closed" value="true" />
<created>1706048258917</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1706048258917</updated>
</task>
<task id="LOCAL-00005" summary="Cleaning up.">
<option name="closed" value="true" />
<created>1706048316975</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1706048316975</updated>
</task>
<task id="LOCAL-00006" summary="Working version ...">
<option name="closed" value="true" />
<created>1706049809584</created>
<option name="number" value="00006" />
<option name="presentableId" value="LOCAL-00006" />
<option name="project" value="LOCAL" />
<updated>1706049809584</updated>
</task>
<task id="LOCAL-00007" summary="Nice and clean ...">
<option name="closed" value="true" />
<created>1706051180665</created>
<option name="number" value="00007" />
<option name="presentableId" value="LOCAL-00007" />
<option name="project" value="LOCAL" />
<updated>1706051180665</updated>
</task>
<task id="LOCAL-00008" summary="Working on concurrency issues.">
<option name="closed" value="true" />
<created>1706114342828</created>
<option name="number" value="00008" />
<option name="presentableId" value="LOCAL-00008" />
<option name="project" value="LOCAL" />
<updated>1706114342829</updated>
</task>
<task id="LOCAL-00009" summary="Fixed concurrency issue.">
<option name="closed" value="true" />
<created>1706152811353</created>
<option name="number" value="00009" />
<option name="presentableId" value="LOCAL-00009" />
<option name="project" value="LOCAL" />
<updated>1706152811353</updated>
</task>
<option name="localTasksCounter" value="10" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" /> <option name="version" value="3" />
</component> </component>
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
<MESSAGE value="Apparently working version." /> <MESSAGE value="Apparently working version." />
<option name="LAST_COMMIT_MESSAGE" value="Apparently working version." /> <MESSAGE value="Final version." />
</component> <MESSAGE value="Minor tweaks" />
<component name="WindowStateProjectService"> <MESSAGE value="Cleaning up." />
<state x="841" y="354" key="#Project_Structure" timestamp="1580086148601"> <MESSAGE value="Working version ..." />
<screen x="0" y="23" width="2560" height="1337" /> <MESSAGE value="Nice and clean ..." />
</state> <MESSAGE value="Working on concurrency issues." />
<state x="841" y="354" key="#Project_Structure/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580086148601" /> <MESSAGE value="Fixed concurrency issue." />
<state x="744" y="354" key="#com.intellij.execution.impl.EditConfigurationsDialog" timestamp="1580060472495"> <option name="LAST_COMMIT_MESSAGE" value="Fixed concurrency issue." />
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state x="744" y="354" key="#com.intellij.execution.impl.EditConfigurationsDialog/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580060472495" />
<state x="998" y="407" key="#com.intellij.ide.util.MemberChooser" timestamp="1580086367455">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state x="998" y="407" key="#com.intellij.ide.util.MemberChooser/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580086367455" />
<state x="980" y="473" key="#com.intellij.ide.util.PackageChooserDialog" timestamp="1580089168186">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state x="980" y="473" key="#com.intellij.ide.util.PackageChooserDialog/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580089168186" />
<state x="-881" y="453" key="#com.intellij.ide.util.TreeClassChooserDialog" timestamp="1580085697611">
<screen x="-2560" y="0" width="2560" height="1440" />
</state>
<state x="-881" y="453" key="#com.intellij.ide.util.TreeClassChooserDialog/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580085697611" />
<state x="822" y="379" key="#com.intellij.openapi.keymap.impl.ui.EditKeymapsDialog" timestamp="1580063056139">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state x="822" y="379" key="#com.intellij.openapi.keymap.impl.ui.EditKeymapsDialog/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580063056139" />
<state x="937" y="450" key="#com.intellij.refactoring.memberPullUp.PullUpDialog" timestamp="1580090901945">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state x="937" y="450" key="#com.intellij.refactoring.memberPullUp.PullUpDialog/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580090901945" />
<state x="1006" y="601" key="#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesDialog.packages" timestamp="1580089169702">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state x="1006" y="601" key="#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesDialog.packages/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580089169702" />
<state x="699" y="23" key="#com.intellij.refactoring.rename.AutomaticRenamingDialog" timestamp="1580087575121">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state x="699" y="23" key="#com.intellij.refactoring.rename.AutomaticRenamingDialog/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580087575121" />
<state x="-815" y="390" key="#xdebugger.evaluate" timestamp="1580069192111">
<screen x="-2560" y="0" width="2560" height="1440" />
</state>
<state x="-815" y="390" key="#xdebugger.evaluate/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580069192111" />
<state x="1025" y="555" key="ANALYSIS_DLG_com.intellij.analysis.BaseAnalysisAction$1" timestamp="1580087667145">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state x="1025" y="555" key="ANALYSIS_DLG_com.intellij.analysis.BaseAnalysisAction$1/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580087667145" />
<state x="1025" y="292" key="CommitChangelistDialog2" timestamp="1580091093422">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state x="1025" y="292" key="CommitChangelistDialog2/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580091093422" />
<state width="2013" height="271" key="GridCell.Tab.0.bottom" timestamp="1580091093452">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state width="2013" height="271" key="GridCell.Tab.0.bottom/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580091093452" />
<state width="2013" height="271" key="GridCell.Tab.0.center" timestamp="1580091093451">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state width="2013" height="271" key="GridCell.Tab.0.center/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580091093451" />
<state width="2013" height="271" key="GridCell.Tab.0.left" timestamp="1580091093450">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state width="2013" height="271" key="GridCell.Tab.0.left/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580091093450" />
<state width="2013" height="271" key="GridCell.Tab.0.right" timestamp="1580091093451">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state width="2013" height="271" key="GridCell.Tab.0.right/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580091093451" />
<state width="2013" height="466" key="GridCell.Tab.1.bottom" timestamp="1580074485214">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state width="2013" height="466" key="GridCell.Tab.1.bottom/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580074485214" />
<state width="2013" height="466" key="GridCell.Tab.1.center" timestamp="1580074485214">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state width="2013" height="466" key="GridCell.Tab.1.center/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580074485214" />
<state width="2013" height="466" key="GridCell.Tab.1.left" timestamp="1580074485213">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state width="2013" height="466" key="GridCell.Tab.1.left/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580074485213" />
<state width="2013" height="466" key="GridCell.Tab.1.right" timestamp="1580074485214">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state width="2013" height="466" key="GridCell.Tab.1.right/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580074485214" />
<state x="-1131" y="242" key="SettingsEditor" timestamp="1580085716244">
<screen x="-2560" y="0" width="2560" height="1440" />
</state>
<state x="-1131" y="242" key="SettingsEditor/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580085716244" />
<state x="1046" y="429" key="Vcs.Push.Dialog.v2" timestamp="1580084921310">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state x="1046" y="429" key="Vcs.Push.Dialog.v2/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580084921310" />
<state x="934" y="525" key="com.intellij.ide.util.TipDialog" timestamp="1580056854823">
<screen x="0" y="23" width="2560" height="1337" />
</state>
<state x="934" y="525" key="com.intellij.ide.util.TipDialog/0.23.2560.1337/-2560.0.2560.1440@0.23.2560.1337" timestamp="1580056854823" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" type="java-line">
<url>file://$PROJECT_DIR$/src/edu/bu/ec504/spr19/sameGameTris/sge.java</url>
<line>81</line>
<option name="timeStamp" value="9" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component> </component>
</project> </project>
\ No newline at end of file
package edu.bu.ec504.spr19.Brain;
import java.util.LinkedList;
import java.util.Objects;
import edu.bu.ec504.spr19.sameGameTris.GUI;
import edu.bu.ec504.spr19.sameGameTris.CircleColor;
/**
* Does whatever a lazy brain does ...
*/
public class lazyBrain extends Brain {
// fields
private volatile boolean allDone = false; // when set to true, the Brain should stop what it's doing and exit (at an appropriate time)
private board currState;
/** Instantiates a Brain linked to a specified myGUI
* @param myGUI The GUI that will be instantiating the Brain
*/
public lazyBrain(GUI myGUI) {
super(myGUI);
}
/**
* {@inheritDoc}
*/
public void allDone() {
allDone = true;
}
/**
* {@inheritDoc}
*/
public String myName() {
return "Greedy Brain";
}
/**
* {@inheritDoc}
*/
public void run() {
// Initialize and set up the board with the current position
currState = new board();
for (int xx=0; xx<myGUI.boardWidth(); xx++)
for (int yy=0; yy<myGUI.boardHeight(); yy++)
currState.modify(xx, yy, myGUI.colorAt(xx, myGUI.boardHeight()-yy-1));
while (!allDone && !myGUI.gameOverQ()) {
pos nextMove = chooseMove();
myGUI.makeMove(nextMove.xx, nextMove.yy); // i.e. click on the lower left corner
}
}
// internal classes
/**
* Stores an (xx,yy) coordinate
*/
static class pos {
final int xx;
int yy;
pos(int xx, int yy) {this.xx=xx; this.yy=yy;}
}
/**
* Stores a board set up
*/
private class board {
final LinkedList< LinkedList <CircleColor>> data;
final private int width, height;
// constructs a board of specified width and height
board(int width, int height) {
this.width = width; this.height = height;
// allocate the data structure
data = new LinkedList<>();
// set up the data structure
for (int ii=0; ii<width; ii++) {
LinkedList<CircleColor> temp = new LinkedList<>();
for (int jj=0; jj<height; jj++)
temp.add(CircleColor.NONE);
data.add(temp);
}
}
/**
* default constructor
*/
board() {
this(myGUI.boardWidth(),myGUI.boardHeight());
}
/**
* copy constructor
* @param basis the board to copy
*/
board(board basis) {
// allocate space
this(basis.width, basis.height);
// copy over all the specific items
for (int xx=0; xx<columns(); xx++)
for (int yy=0; yy<rows(xx); yy++)
modify(xx,yy,basis.get(xx, yy));
}
/**
* @return the color of the yy'th item in the xx'th column, or CircleColor.NONE
* if the cell is not in the <b>data</b> container
*/
private CircleColor get(int xx, int yy) {
try {
return data.get(xx).get(yy);
} catch (IndexOutOfBoundsException e) {
return CircleColor.NONE;
}
}
/**
* @return the xx'th column, or null if the xx'th column does not exist in the <b>data</b> container
*/
private LinkedList<CircleColor> get(int xx) {
try {
return data.get(xx);
} catch (IndexOutOfBoundsException e) {
return null;
}
}
/**
* Changes yy'th item in xx'th column
* %R: The circle at this location must still be in the <b>data</b> field
*/
private void modify(int xx, int yy, CircleColor col) {
data.get(xx).set(yy, col);
}
/**
* Deletes yy'th item in xx'th column
* %R: The circle at this location must still be in the <b>data</b> field
*/
private void delete(int xx, int yy) {
data.get(xx).remove(yy);
}
/**
* Deletes the column at (horizontal) board location xx
* * %R: The column at this location must still be in the <b>data</b> field
*/
private void delete(int xx) {
data.remove(xx);
}
// public accessors
/**
* Simulates a click on the yy'th cell of the xx'th column
* @return the number of cells deleted
* @modifies Deletes cells in the same region as cell (xx,yy)
* @expects The clicked cell is assumed not to be empty
*/
int clickNode(int xx, int yy) {
CircleColor myColor = get(xx,yy); // the color to match
// mark the region
int count = clickNodeHelper(xx, yy, myColor);
// clean up
// ... delete all cells with no color
// ... delete from the end to the front so that there are no problems with re-indexing
for (int ii=data.size()-1; ii>=0; ii--) {
for (int jj = Objects.requireNonNull(get(ii)).size()-1; jj>=0; jj--)
if (get(ii,jj)==CircleColor.NONE)
delete(ii, jj);
// ... delete the column if it is empty
if (Objects.requireNonNull(get(ii)).size()==0)
delete(ii);
}
return count;
}
/**
* Recursive procedure for propagating a click at a location with color "col".
* All items in the same region as the clicked cell are made to have CircleColor.NONE
* @modifies the color of some cells, but no cells are actually deleted
* @return the number of cells changed to CircleColor.NONE in this call (and its recursive subcalls)
*/
private int clickNodeHelper(int xx, int yy, CircleColor col) {
if (get(xx,yy) == CircleColor.NONE || // we've either already seen this, or we've hit an empty space
get(xx,yy) != col) // this is not the color we want
return 0;
// modify the current cell
modify(xx,yy,CircleColor.NONE);
return 1 + // 1 is for the current cell
clickNodeHelper(xx-1, yy, col) + // cell to the left
clickNodeHelper(xx+1, yy, col) + // cell to the right
clickNodeHelper(xx, yy-1, col) + // cell below
clickNodeHelper(xx, yy+1, col) // cell above
;
}
/**
* @return the number of columns in the current state
*/
int columns() {
return data.size();
}
/**
*
* @param xx the column being considered (assumed to exist)
* @return the number of rows in column <b>xx</b>
*/
int rows(int xx) {
return data.get(xx).size();
}
/**
* @return a "pretty-printed" version of the data structure
*/
public String display() {
String temp = data.toString();
return temp.replace("], [", "]\n[");
}
}
// internal methods
/**
* Chooses the next move to make.
* @return the move chosen, in GUI coordinates
*/
private pos chooseMove() {
// greedy choice
int max=0; // the maximum number of points for the best position found
pos bestPos = new pos(0, 0); // the best position found
board currStateCopy = new board(currState);
for (int xx=0; xx<currState.columns(); xx++)
for (int yy=0; yy<currState.rows(xx); yy++) {
if (currStateCopy.get(xx,yy)!= CircleColor.NONE) {
board test = new board(currStateCopy);
currStateCopy.clickNodeHelper(xx, yy, test.get(xx,yy)); // mark all other nodes in the region as "clear" (but does not delete anything)
int count = test.clickNode(xx, yy); // try removing the region to see what is left over
if (count > max) {
// record a new best move
max = count;
bestPos = new pos(xx, yy);
}
}
}
// register the selected move on the board
currState.clickNode(bestPos.xx, bestPos.yy);
// convert bestPos to GUI coordinates
bestPos.yy = myGUI.boardHeight() - 1 - bestPos.yy;
// return the result to the GUI
return bestPos;
}
}
package edu.bu.ec504.spr19.Brain;
import edu.bu.ec504.spr19.sameGameTris.GUI;
/**
* A very simple Brain for the game.
*/
public class simpleBrain extends Brain {
// fields
private volatile boolean allDone = false; // when set to true, the Brain should stop what it's doing and exit (at an appropriate time)
public simpleBrain(GUI myGUI) {
super(myGUI);
}
/**
* {@inheritDoc}
*/
public void allDone() {
allDone = true;
}
/**
* {@inheritDoc}
*/
public String myName() {
return "Simple Brain";
}
/**
* {@inheritDoc}
*/
public void run() {
while (!allDone && !myGUI.gameOverQ())
myGUI.makeMove(0, myGUI.boardHeight()-1); // i.e. click on the lower left corner
}
}
\ No newline at end of file
package edu.bu.ec504.spr19.sameGameTris;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/*
* An extension of JButton that keeps track of its own location on a grid
*/
class SelfAwareCircle extends Component implements MouseListener, SelfAwareListener {
// Constants
private final Color hltColor = new Color(200,200,200); // the color to use for highlighting a button
// Data fields
public final int xx;
public final int yy;
public CircleColor clr;
private static final long serialVersionUID = 1L;
private final ArrayList<SelfAwareListener> _listeners = new ArrayList<>(); // who is listening to events fired by this circle
private static int id=0;
private final int myid; // the circle's ID
private Visibility state = Visibility.plain; // the current state of the circle
private final sge sgeGui; // a link to the GUI that created this circle
// concurrency variables
private static final AtomicInteger regionLength = new AtomicInteger(0);
// SUBCLASSES
/*
* Records the length of the selected region
*/
public enum Visibility{
/*
* the circle is highlighted
*/
highlight,
/*
* the circle is plain (i.e. the default)
*/
plain,
/*
* the circle is cleared (i.e. background color)
*/
clear}
// CONSTRUCTORS
/**
* Creates a circle of the specified color at the specified location
* @param clr The color of the new circle.
* @param xx The xx location of the circle in the grid of circles.
* @param yy The yy location of the circle in the grid of circles.
* @param sgeGui A reference to the game GUI in which the circle will lie.
*/
SelfAwareCircle(CircleColor clr, int xx, int yy, sge sgeGui) {
// record field parameters
this.xx = xx;
this.yy = yy;
this.clr = clr; // record the color of the button
if (clr == CircleColor.NONE) // circles with no color should be cleared
setClear();
this.sgeGui = sgeGui; // record the GUI that created this button
addMouseListener(this); // register for mouse events
myid = id++;
}
/**
* Creates a circle of the specified color at the specified location, based on {@link #SelfAwareCircle(CircleColor, int, int, sge).}
*/
SelfAwareCircle(int xx, int yy, sge mySgeGui) {
this(CircleColor.randColor(), xx, yy, mySgeGui);
}
// METHODS
// ... helpers
// .. info methods and accessors/setters
static int getRegionLength() {
return regionLength.get();
}
public int getId() {
return myid;
}
Visibility getState() {
return state;
}
public CircleColor getColor() {
return clr;
}
/**
* Resets the color of this circle to a random color.
*/
void resetColor() {
clr=CircleColor.randColor();
}
void setState (Visibility state) {
if (state==Visibility.clear)
setClear(); // i.e. there is extra overhead in clearing
else
this.state = state;
repaint();
}
boolean isCleared() {
return this.state == Visibility.clear;
}
void setClear() {
state = Visibility.clear;
repaint();
}
/*
* Copies immutable elements of this SelfAwareCircle to "recipient"
* *except* position-dependent items (e.g. _listeners, xx, yy)
*/
void copy(SelfAwareCircle recipient) {
recipient.clr=clr;
if (recipient.state == state)
return; // i.e. nothing to do
// special cases
// ... copying a cleared circle to an uncleared circle
if (state == Visibility.clear)
recipient.setClear(); // includes removing a mouse listener
// copy the state
recipient.setState(state);
}
// ... Mouse methods
public void mouseClicked(MouseEvent e) {}
/*
* Highlights the button by changing its icon and firing off an event
* @param state determines whether to highlight or unhighlight the circle
*/
private void highlightButton(Visibility stt) {
if (isCleared()) // don't go further with already cleared circles
return;
// set the state
if (stt==Visibility.clear) // clear has its own special treatment
setClear(); // properly clear this circle (including removal of mouse listener)
else
setState(stt); // set the non-cleared state appropriately
// deal with the region length
regionLength.getAndIncrement(); // update the region length by the newly highlighted item
// announce the rollover to other circles
_fireButtonRolloverEvent(stt);
}
/*
* Mouse entered the object area:
* * Change its button icon
* * Broadcast the change to other buttons
* @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
*/
public void mouseEntered(MouseEvent e) {
regionLength.set(0); // i.e. reinitialize
highlightButton(Visibility.highlight);
}
public void mouseExited(MouseEvent e) {
regionLength.set(0); // i.e. reinitialize
highlightButton(Visibility.plain);
}
public void mousePressed(MouseEvent e) {
regionLength.set(0); // i.e. reinitialize
}
public void mouseReleased(MouseEvent e) {
highlightButton(Visibility.clear);
// register the score
sgeGui.totalPoints.setText("" +
(Integer.parseInt(sgeGui.totalPoints.getText()) +
sgeGui.score(SelfAwareCircle.getRegionLength(), getColor())));
// request that the board be shifted appropriately
sgeGui.shiftCircles();
// update tetrising
sgeGui.updateNumClicks();
sgeGui.updateTetrising();
}
// ... event methods
public synchronized void addSelfAwareListener(SelfAwareListener l) {
_listeners.add(l);
}
public synchronized void removeSelfAwareListener(SelfAwareListener l) {
_listeners.remove(l);
}
/*
* fires off events to highlight or unhighlight circles
* @param state determines whether to highlight or unhighlight circles
*/
private synchronized void _fireButtonRolloverEvent(SelfAwareCircle.Visibility state) {
CircleRolloverEvent bre = new CircleRolloverEvent(this, xx, yy, clr, state);
for (SelfAwareListener _listener : _listeners) {
SelfAwareCircle temp = (SelfAwareCircle) _listener;
temp.rollingOver(bre);
}
}
/*
* What to do when receiving information from another button of a rolling over event
* @param e the event broadcast by the other button
*/
public void rollingOver(CircleRolloverEvent e) {
if (state == e.getAction() || isCleared()) // i.e. this circle has already been processed
return;
if (e.getColor().equals(clr)) { // check if the color is the same as mine
highlightButton(e.getAction());
}
// in all other cases, only update the text field (i.e. stop the recursion)
sgeGui.regionPoints.setText(""+(SelfAwareCircle.getRegionLength())); // update the gui text field
}
// ... drawing methods
public void paint(Graphics g) {
// set the color, depending on whether this is being highlighted
switch (state) {
case highlight:
g.setColor(hltColor); // change to highlighting color
break;
case plain:
g.setColor(clr.getColor()); // the default color
break;
case clear:
g.setColor(getParent().getBackground()); // i.e. set to background color
break;
}
// size of component
Dimension size = getSize();
// draw the circle
g.fillOval(0, 0, size.width, size.height);
}
}
package edu.bu.ec504.spr19.sameGameTris;
/* An implementation of the "samegnome" game, possibly with a computer-aided solver.
Written by Prof. Ari Trachtenberg for EC504 at Boston University.
*/
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.*;
import java.util.Random;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.ButtonGroup;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import edu.bu.ec504.spr19.Brain.smarterBrain;
import edu.bu.ec504.spr19.highScores.highScore;
import edu.bu.ec504.spr19.Brain.Brain;
import edu.bu.ec504.spr19.Brain.lazyBrain;
import edu.bu.ec504.spr19.Brain.simpleBrain;
import static java.util.concurrent.Executors.newScheduledThreadPool;
class sge extends GUI implements ActionListener, ItemListener {
// CONSTANTS
static public final int defaultWidth = 15, defaultHeight = 10; // sizes in terms of numbers of circles
static public final int defaultEmptyRows = defaultHeight / 3; // number of empty rows on the top of the game
static public final int defaultWindowWidth = 500, defaultWindowHeight = 300; // default window size, if run as a standalone application
static public final int numColors = 3; // the number of colors available for circles
static private final int displayTime = 1000; // number of milliseconds to display (highlight) a move before actually making it
static private final String highScoreFile = ".highscores.db"; // where high scores are kept
static private final long serialVersionUID = 1L; // required for serializability
// FIELDS (all static - only one instance of the game should be running at a time)
final JLabel regionPoints = new JLabel("0"); // keeps track of the number of selected points
final JLabel totalPoints = new JLabel("0"); // keeps track of the total number of points so far
private int width = defaultWidth, height = defaultHeight; // initial width and height of the array of circles
private int emptyRows = defaultEmptyRows; // number of rows on top of screen with no circles
private SelfAwareCircle[][] circles;
private String highScoreName = null; // the name to use for the high score
static final Random randGen = new Random(); // package-private random number generator used for the entire package (i.e., repeatedly seeding with the same value produces the same randomness).
private int numClicks = 1; // Must be a positive integer; counts the number of clicks on circles since the last time the board was reset.
// ... GUI elements
private JPanel circlePanel; // the panel containing the circles
private JPanel textPanel; // the panel containing scoring information
// ... ... menu items
private JMenuItem changeBoard;
private JMenuItem quitGame;
private JRadioButtonMenuItem noPlayerMenuItem, simplePlayerMenuItem, lazyPlayerMenuItem, // various automatic player options
smarterPlayerMenuItem;
private ReentrantLock GUIlock = new ReentrantLock(); // thread lock used to synchronize manipulation of circles on the board
// ... Brain elements
Brain theBrain; // the Brain (automated player) for the game
Thread brainThread; // the thread that will run the Brain
// ... Tetris executor
final ScheduledExecutorService tetrisExec=newScheduledThreadPool(2); // sets up tetris threads
private ScheduledFuture<?> produceCircleFuture=null; // schedule for producing circles for the app
private ScheduledFuture<?> dropCircleFuture=null; // schedule for dropping circles for the app
// SUBCLASSES
/**
* Drops a new circle at the top of the board every time it is run.
* If this is not possible, then the gameOverSignal is set to true
*/
private class initDropCircles implements Runnable {
@Override
public void run() {
GUIlock.lock(); // lock circle manipulations down
try {
int randColumn = randGen.nextInt(width);
SelfAwareCircle myCircle = circles[randColumn][0];
if (myCircle.getState() != SelfAwareCircle.Visibility.clear) { // i.e. the spot is already occupied
doGameOver("overran column "+randColumn);
}
else {
myCircle.resetColor();
myCircle.setState(SelfAwareCircle.Visibility.plain);
}
} finally {
GUIlock.unlock();
}
repaint();
}
}
/**
* Moves circles down until they are connected to the main collection of circles.
*/
private class moveDropCircles implements Runnable {
@Override
public void run() {
GUIlock.lock(); // lock circle manipulations down
try {
// hunt for a circle that is above a clear circle
for (int xx = 0; xx < width; xx++)
for (int yy = height - 2; yy >= 0; yy--) { // go down to up, so as not to move circles more than once
SelfAwareCircle orig = circles[xx][yy],
dest = circles[xx][yy + 1];
if (orig.getState() != SelfAwareCircle.Visibility.clear && // a colored circle
dest.getState() == SelfAwareCircle.Visibility.clear) { // above a cleared circle
// move it down
moveCircle(orig, dest);
}
}
}
finally {
GUIlock.unlock();
}
repaint();
}
}
// PUBLIC ACCESS METHODS
/*
* Default no-args constructor
*/
public sge() {
super("Ari's samegame");
try {
SwingUtilities.invokeAndWait(
this::setupGUI
);
} catch (Exception e) {
System.out.println("Saw exception " + e);
}
}
/*
* Returns the color of the circle at location [xx][yy], or NONE if the circle has been cleared
* @param xx must be between 0 and width
* @param yy must be between 0 and height
*/
public CircleColor colorAt(int xx, int yy) {
if (circles[xx][yy].isCleared())
return CircleColor.NONE;
else
return circles[xx][yy].clr;
}
/*
* Returns the width of the current board
*/
public int boardWidth() {
return width;
}
/*
* Returns the height of the current board
*/
public int boardHeight() {
return height;
}
/*
* Returns true iff the game is over
* (i.e. every circle is surrounded by cleared circles or circles of a different color)
*/
public boolean gameOverQ() {
for (int xx = 0; xx < width; xx++)
for (int yy = 0; yy < height; yy++) {
if (!circles[xx][yy].isCleared()) {
CircleColor myColor = circles[xx][yy].getColor();
// check its neighbors
if (sameColor(myColor, xx - 1, yy) ||
sameColor(myColor, xx + 1, yy) ||
sameColor(myColor, xx, yy - 1) ||
sameColor(myColor, xx, yy + 1))
return false; // the game has not ended
}
}
return true; // there are no viable moves
}
/*
* "Clicks" on the circle at location (xx,yy)
*/
public void makeMove(int xx, int yy) {
circles[xx][yy].mouseEntered(null); // pretend the mouse was pressed at location (xx,yy)
try {
Thread.sleep(displayTime); // wait a bit for the user to register the move
} catch (InterruptedException ignored) {
}
circles[xx][yy].mouseExited(null);
circles[xx][yy].mousePressed(null);
circles[xx][yy].mouseReleased(null); // pretend that the mouse button was released at the location
}
/**
* @return the score achieved from clicking on a region of length <b>level</b>
*/
final public int score(int level, CircleColor clr) {
if (level == 1)
return 1;
else
return level * level;
}
// OTHER METHODS
/*
* setupGUI helper
* - adds to circle[ii][jj] a SelfAwareListener of circle[xx][yy] as 0<=xx<width and 0<=yy<height
* @param ii = x coordinate of the circle to whom we will be listening
* @param jj = y coordinate of the circle to whom we will be listening
* @param xx = x coordinate of the listener circle
* @param yy = y coordinate of the listener circle
*/
private void addNeighbor(int ii, int jj, int xx, int yy) {
if ((0 <= xx && xx < width) &&
(0 <= yy && yy < height))
circles[ii][jj].addSelfAwareListener(circles[xx][yy]);
}
/**
* Setup the initial screen items
* NOTE: must perform cleanUp() first if there is already a GUI set up on the screen
*/
void setupGUI() {
// SET LAYOUT STYLE
setLayout(new BorderLayout());
// SET UP MENUS
JMenuBar menuBar = new JMenuBar();
// ... File Menu
JMenu menu = new JMenu("File");
changeBoard = new JMenuItem("Change board");
changeBoard.addActionListener(this);
menu.add(changeBoard);
quitGame = new JMenuItem("Quit");
quitGame.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.META_MASK));
quitGame.setMnemonic('Q');
quitGame.addActionListener(this);
menu.add(quitGame);
menuBar.add(menu);
// ... Automatic play Menu
menu = new JMenu("Automatic play");
ButtonGroup group = new ButtonGroup();
noPlayerMenuItem = new JRadioButtonMenuItem("none");
noPlayerMenuItem.addItemListener(this);
noPlayerMenuItem.setSelected(true); // i.e. the default item
group.add(noPlayerMenuItem);
menu.add(noPlayerMenuItem);
menu.addSeparator();
simplePlayerMenuItem = new JRadioButtonMenuItem("Simple player");
simplePlayerMenuItem.addItemListener(this);
group.add(simplePlayerMenuItem);
menu.add(simplePlayerMenuItem);
lazyPlayerMenuItem = new JRadioButtonMenuItem("Lazy player");
lazyPlayerMenuItem.addItemListener(this);
group.add(lazyPlayerMenuItem);
menu.add(lazyPlayerMenuItem);
smarterPlayerMenuItem = new JRadioButtonMenuItem("Smarter player");
smarterPlayerMenuItem.addItemListener(this);
group.add(smarterPlayerMenuItem);
menu.add(smarterPlayerMenuItem);
menuBar.add(menu);
this.setJMenuBar(menuBar);
// SET UP CIRCLE PANEL
circlePanel = new JPanel(new GridLayout(height, width));
// ... allocate circles
circles = new SelfAwareCircle[width][height];
// ..... set up some circles
for (int jj = 0; jj < height; jj++)
for (int ii = 0; ii < width; ii++) {
// establish the button
if (jj<=emptyRows)
circles[ii][jj] = new SelfAwareCircle(CircleColor.NONE, ii, jj, this); // the top emptyRows should have a circle with no color
else
circles[ii][jj] = new SelfAwareCircle(ii, jj, this);
circlePanel.add(circles[ii][jj]);
}
// ... set up listeners in the area
for (int xx = 0; xx < width; xx++)
for (int yy = 0; yy < height; yy++) {
// add all neighbors
addNeighbor(xx, yy, xx, yy - 1);
addNeighbor(xx, yy, xx, yy + 1);
addNeighbor(xx, yy, xx - 1, yy);
addNeighbor(xx, yy, xx + 1, yy);
}
add(circlePanel, BorderLayout.CENTER);
// ... set up the text panel
textPanel = new JPanel(new FlowLayout());
textPanel.add(new JLabel("Selected len: "));
textPanel.add(regionPoints);
textPanel.add(new JLabel("")); // i.e. blank
textPanel.add(new JLabel("Total pts: "));
textPanel.add(totalPoints);
add(textPanel, BorderLayout.SOUTH);
// paint the display
validate();
repaint();
}
/**
* Increment the number of clicks seen.
* If this ends up being greater than the size of the board, the game is declared over.
*/
public void updateNumClicks() {
numClicks++;
if (numClicks>width*height)
doGameOver("too many clicks - "+numClicks);
}
/**
* Cancel schedules for adding / dropping circles.
*/
private void cancelTetrisingSchedules() {
if (produceCircleFuture!=null)
produceCircleFuture.cancel(true);
if (dropCircleFuture!=null)
dropCircleFuture.cancel(true);
}
/**
* Sets up threads that drop circles onto the board and move them down over time.
* As {@link #numClicks} gets larger, circles come more often and drop faster.
*/
public void updateTetrising() {
// cancel the old schedules
cancelTetrisingSchedules();
// start up the new ones
produceCircleFuture = tetrisExec.scheduleAtFixedRate(new initDropCircles(), 0, 5000000 / numClicks, TimeUnit.MICROSECONDS);
dropCircleFuture = tetrisExec.scheduleAtFixedRate(new moveDropCircles(), 0, 2000000 / numClicks, TimeUnit.MICROSECONDS);
}
/*
* Performs any clean up actions needed before setting up a new GUI
*/
void cleanUp() {
// Removes elements for garbage collection
// close down the brain
highScoreName = null;
if (theBrain != null)
theBrain.allDone();
// score panel
totalPoints.setText("0");
textPanel.setVisible(false);
textPanel = null;
// circles
for (int ii = 0; ii < width; ii++)
for (int jj = 0; jj < height; jj++) {
circles[ii][jj].removeMouseListener(circles[ii][jj]);
circles[ii][jj] = null;
}
// circle panel
circlePanel.setVisible(false);
circlePanel = null;
// cancel tetrising schedules
numClicks=1; // reset the number of circle clicks in the system
cancelTetrisingSchedules();
}
/*
* Returns true iff circles[xx][yy] has color theColor, is not cleared, AND (xx,yy) is within the range of the board
*/
private boolean sameColor(CircleColor theColor, int xx, int yy) {
if (xx < 0 || yy < 0 || xx >= width || yy >= height || circles[xx][yy].isCleared())
return false;
else
return circles[xx][yy].getColor().equals(theColor);
}
/*
* Moves circle c1 into location c2, leaving c1 as a clear circle that
* does not receive mouse events
*/
synchronized private void moveCircle(SelfAwareCircle orig, SelfAwareCircle dest) {
// copy the immutable, position independent values
orig.copy(dest);
// clear the top item
orig.setClear();
}
/*
* Called to request a reshifting of the board (as necessary).
* This should happen if some circles are rendered "clear"ed
*/
final void shiftCircles() {
/* start at the bottom and move up ... all cleared circles are
* removed, with upper circles falling into their positions;
* if a column is totally empty, then its rightmost columns
* shift into it
*/
GUIlock.lock(); // acquire a lock
try {
// 1. SHIFT VERTICALLY
for (int xx = 0; xx < width; xx++) {
int firstClr = height - 1; // the lowest cleared entry in the column
int firstFull = height - 1; // the lowest uncleared entry in the column that has not yet been processed
boolean moveOn = false; // set to true in order to move on to the next column
while (!moveOn) {
// find the lowest clear entry in the column (if it exists)
try {
while (!circles[xx][firstClr].isCleared())
firstClr--;
} catch (ArrayIndexOutOfBoundsException e) {
moveOn = true;
continue; // i.e. no cleared circle found in this column --- go to the next column
}
if (firstFull > firstClr)
firstFull = firstClr; // only move items "down" the column
// find the lowest non-cleared entry in the column (if it exists)
try {
while (circles[xx][firstFull].isCleared())
firstFull--;
} catch (ArrayIndexOutOfBoundsException e) {
moveOn = true;
continue; // i.e. the whole column is clear --- for now, go to the next column
}
moveCircle(circles[xx][firstFull], circles[xx][firstClr]);
firstFull--;
firstClr--; // iterate
}
}
// 2. SHIFT HORIZONTALLY
// Check to see if any column is now empty
// ... this could have been done within the loop above, but it would detract from readability of the code
boolean emptySoFar = true; // remains true if all columns seen so far have only cleared circles
for (int xx = width - 1; xx >= 0; xx--) {
boolean allCleared = true; // remains true if all circles in column xx have been cleared
for (int yy = 0; yy < height; yy++)
if (!circles[xx][yy].isCleared()) {
allCleared = false;
break;
}
if (allCleared) {
if (!emptySoFar) { // i.e. do not do anything with empty columns on the right of the screen
// move other columns into this empty column
for (int ii = xx + 1; ii < width; ii++)
for (int jj = 0; jj < height; jj++)
moveCircle(circles[ii][jj], circles[ii - 1][jj]);
}
} else
emptySoFar = false;
}
// 3. (LAST ITEM) CHECK IF THE GAME HAS ENDED
// This happens if every circle is surrounded by cleared circles or circles of a different color
if (gameOverQ())
doGameOver("exhausted all circles"); // all done
}
finally {
GUIlock.unlock(); // release the circles for other processes
}
}
/**
* The game is over - report to the user and/or save a high score, if relevant.
* @param feedback Some string to present to the user; typically the reason that the game is over.
*/
private void doGameOver(String feedback) {
cancelTetrisingSchedules(); // cancel tetrising threads
int score = new Integer(totalPoints.getText());
highScore hs = new highScore(highScoreFile);
// check the high score
if (hs.newRecordQ(score)) { // i.e. a new record
JOptionPane.showMessageDialog(null, "Game Over ("+feedback+") - You got a new high score of " + score + " points!\n" +
"Your weighted score for this sized board was " + highScore.hsComp.weightedScore(new highScore.HSdata("", width, height, score)) + ".");
if (highScoreName == null)
highScoreName = JOptionPane.showInputDialog(null, "You got a new high score!\nPlease enter your name:");
if (highScoreName != null) { // i.e. the user is interested in high scores
// populate the high score item
highScore.HSdata datum = new highScore.HSdata(highScoreName, width, height, score);
hs.putScore(datum); // enter the name into the high score list
}
} else
JOptionPane.showMessageDialog(null, "Game Over ("+feedback+") - You did not make the high score. You had " + score + " points.\n" +
"Your weighted score for this sized board was " + highScore.hsComp.weightedScore(new highScore.HSdata("", width, height, score)) + ".");
JOptionPane.showMessageDialog(null, "Current high scores:\n" + hs.display());
// i.e. time for a new game
cleanUp();
setupGUI();
}
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source == quitGame)
System.exit(0);
else if (source == changeBoard) {
// modified from http://www.vbforums.com/showthread.php?t=513699
JTextField width = new JTextField();
width.setText("" + defaultWidth);
JTextField height = new JTextField();
height.setText("" + defaultHeight);
Object[] msg = {"Width:", width, "Height:", height};
JOptionPane op = new JOptionPane(
msg,
JOptionPane.QUESTION_MESSAGE,
JOptionPane.OK_CANCEL_OPTION,
null,
null);
JDialog dialog = op.createDialog(this, "Enter new board size ...");
dialog.setVisible(true);
int result = JOptionPane.OK_OPTION;
try {
result = (Integer) op.getValue();
} catch (Exception ignored) {
}
if (result == JOptionPane.OK_OPTION) // i.e. effect the change
{
// i.e. destroy the old board
cleanUp();
this.width = new Integer(width.getText());
this.height = new Integer(height.getText());
// create the new board
setupGUI();
}
}
}
/**
* Starts a Brain that makes moves
*/
private void startBrain(Brain theBrain) {
this.theBrain = theBrain;
highScoreName = theBrain.myName(); // the computer gets credit for any subsequent high score
brainThread = new Thread(theBrain, "Brain Thread");
brainThread.start();
}
// For handling the menu checkBox
public void itemStateChanged(ItemEvent e) {
Object source = e.getItemSelectable();
if (e.getStateChange() == ItemEvent.SELECTED) {
if (source == noPlayerMenuItem)
return; // i.e. no players
else if (source == simplePlayerMenuItem)
startBrain(new simpleBrain(this));
else if (source == lazyPlayerMenuItem)
startBrain(new lazyBrain(this));
else if (source == smarterPlayerMenuItem)
startBrain(new smarterBrain( this));
} else {
// deselected
if (theBrain != null)
theBrain.allDone(); // will ask the Brain to stop thinking
}
}
// MAIN METHOD
/**
* Runs the application
*/
public static void main(String[] args) {
JFrame myApp = new sge();
myApp.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myApp.setSize(defaultWindowWidth, defaultWindowHeight);
myApp.setVisible(true);
}
}
\ No newline at end of file
package edu.bu.ec504.spr24.brain;
import edu.bu.ec504.spr24.sameGameTris.CircleColor;
import edu.bu.ec504.spr24.sameGameTris.SameGameTris;
import java.util.LinkedList;
import java.util.Objects;
/**
* An internal recording of a board set up.
*/
class Board {
/**
* A 2-d Linked list of colors on the board.
*/
final LinkedList<LinkedList<CircleColor>> data;
/**
* The width and height of the board.
*/
final private int width, height;
// constructs a board of specified width and height
Board(int width, int height) {
this.width = width;
this.height = height;
// allocate the data structure for storing board contents
data = new LinkedList<>();
// set up the data structure
for (int ii = 0; ii < width; ii++) {
LinkedList<CircleColor> temp = new LinkedList<>();
for (int jj = 0; jj < height; jj++) {temp.add(CircleColor.NONE);}
data.add(temp);
}
}
/**
* default constructor
*/
Board() {
this(SameGameTris.getInstance().boardWidth(), SameGameTris.getInstance().boardHeight());
}
/**
* copy constructor
*
* @param baseBoard the board to copy
*/
Board(Board baseBoard) {
// allocate memory for the new board
this(baseBoard.width, baseBoard.height);
// copy over all the specific items
for (int xx = 0; xx < columns(); xx++) {
for (int yy = 0; yy < rows(xx); yy++) {modify(xx, yy, baseBoard.getAt(xx, yy));}
}
}
/**
* @return the color of the yy'th item in the xx'th column, or CircleColor.NONE
* if the cell is not in the <b>data</b> container
*/
CircleColor getAt(int xx, int yy) {
try {
return data.get(xx).get(yy);
} catch (IndexOutOfBoundsException e) {
return CircleColor.NONE;
}
}
/**
* @return the xx'th column, or null if the xx'th column does not exist in the <b>data</b> container
*/
private LinkedList<CircleColor> getAt(int xx) {
try {
return data.get(xx);
} catch (IndexOutOfBoundsException e) {
return null;
}
}
/**
* Changes yy'th item in xx'th column
* %R: The circle at this location must still be in the <b>data</b> field
*/
void modify(int xx, int yy, CircleColor col) {
data.get(xx).set(yy, col);
}
/**
* Deletes yy'th item in xx'th column
* %R: The circle at this location must still be in the <b>data</b> field
*/
private void delete(int xx, int yy) {
data.get(xx).remove(yy);
}
/**
* Deletes the column at (horizontal) board location xx
* * %R: The column at this location must still be in the <b>data</b> field
*/
private void delete(int xx) {
data.remove(xx);
}
// public accessors
/**
* Simulates a click on the yy'th cell of the xx'th column
*
* @return the number of cells deleted
* @modifies Deletes cells in the same region as cell (xx,yy)
* @expects The clicked cell is assumed not to be empty
*/
int clickNode(int xx, int yy) {
CircleColor myColor = getAt(xx, yy); // the color to match
// mark the region
int count = clickNodeHelper(xx, yy, myColor);
// clean up
// ... delete all cells with no color
// ... delete from the end to the front so that there are no problems with re-indexing
for (int ii = data.size() - 1; ii >= 0; ii--) {
for (int jj = Objects.requireNonNull(getAt(ii)).size() - 1; jj >= 0; jj--) {
if (getAt(ii, jj) == CircleColor.NONE) {delete(ii, jj);}
}
// ... delete the column if it is empty
if (Objects.requireNonNull(getAt(ii)).isEmpty()) {delete(ii);}
}
return count;
}
/**
* Recursive procedure for propagating a click at a location with color "col".
* All items in the same region as the clicked cell are made to have CircleColor.NONE
*
* @return the number of cells changed to CircleColor.NONE in this call (and its recursive subcalls)
* @modifies the color of some cells, but no cells are actually deleted
*/
int clickNodeHelper(int xx, int yy, CircleColor col) {
if (getAt(xx, yy) == CircleColor.NONE ||
// we've either already seen this, or we've hit an empty space
getAt(xx, yy) != col) // this is not the color we want
{return 0;}
// modify the current cell
modify(xx, yy, CircleColor.NONE);
return 1 + // 1 is for the current cell
clickNodeHelper(xx - 1, yy, col) + // cell to the left
clickNodeHelper(xx + 1, yy, col) + // cell to the right
clickNodeHelper(xx, yy - 1, col) + // cell below
clickNodeHelper(xx, yy + 1, col) // cell above
;
}
/**
* @return the number of columns in the current state
*/
int columns() {
return data.size();
}
/**
* @param xx the column being considered (assumed to exist)
* @return the number of rows in column <b>xx</b>
*/
int rows(int xx) {
return data.get(xx).size();
}
/**
* @return a "pretty-printed" version of the data structure
*/
@Override
public String toString() {
String temp = data.toString();
return temp.replace("], [", "]\n[");
}
/**
* Stores an (xx,yy) position on the board.
*/
static class Pos {
final int xx;
final int yy;
Pos(int xx, int yy) {
this.xx = xx;
this.yy = yy;
}
@Override
public String toString() {
return "[" + xx + ", " + yy +']';
}
}
}
package edu.bu.ec504.spr19.Brain; package edu.bu.ec504.spr24.brain;
import edu.bu.ec504.spr19.sameGameTris.GUI; import edu.bu.ec504.spr24.sameGameTris.GUI;
import edu.bu.ec504.spr24.sameGameTris.SameGameTris;
/** /**
* The Brain is the artificial intelligence that tries to come up with the * The Brain is the artificial intelligence that tries to come up with the
* best possible moves for the current position. * best possible moves for the current position.
* * <p>
* It typically runs in its own thread so that it will not interfere with other processing. * It typically runs in its own thread so that it will not interfere with other processing.
*/ */
public abstract class Brain implements Runnable { public abstract class Brain implements Runnable {
// fields
protected final GUI myGUI; // the GUI class attached to this Brain // CONSTANTS
// methods // FIELDS
protected final GUI myGUI; // stores the GUI class attached to this Brain
/**
* When set to true, the Brain should stop what it's doing and exit as soon as is appropriate.
*/
private volatile boolean allDone = false;
// METHODS
/** /**
* Constructor - records the GUI attached to this brain * Constructs the brain
*/ */
Brain(GUI myGUI) { this.myGUI = myGUI; } Brain() {this.myGUI = SameGameTris.getInstance();}
/** /**
* This is called when the Brain is being asked to close down. * This is called when the Brain is being asked to close down (i.e., the game is over).
* It should clean up any data structures and/or signal threads to close, etc. * It should clean up any data structures and/or signal threads to close, etc.
*/ */
public abstract void allDone(); final public void allDone() {allDone = true;}
/** /**
* Asks the Brain to for a move, and effects it on the GUI.
*
* @see java.lang.Runnable#run()
*/
final public void run() {
if (!allDone && !myGUI.gameOverQ()) {
Board.Pos theNextMove = nextMove();
myGUI.makeMove(theNextMove.xx, theNextMove.yy);
}
}
// ... METHODS FOR SUBCLASSES TO DEFINE
/**
* Each Brain should have a name, which is provided by this method.
*
* @return the name of the brain * @return the name of the brain
*/ */
public abstract String myName(); public abstract String myName();
/** /**
* Starts the Brain a'thinking. * Returns the next move of the Brain.
* @see java.lang.Runnable#run()
*/ */
public abstract void run(); abstract Board.Pos nextMove();
} }
package edu.bu.ec504.spr24.brain;
import edu.bu.ec504.spr24.brain.Board.Pos;
import edu.bu.ec504.spr24.sameGameTris.CircleColor;
/**
* Does whatever a lazy brain does ...
*/
public class LazyBrain extends Brain {
// FIELDS
/**
* The Brain's recording of the current state of the board.
*/
private Board currBoard;
// METHODS
/**
* Constructs a Brain linked to a specified myGUI
*/
public LazyBrain() {
super();
}
/**
* {@inheritDoc}
*/
public String myName() {
return "Lazy Brain";
}
@Override
Pos nextMove() {
// Initialize and set up the internal board state with the current position
currBoard = new Board();
for (int xx = 0; xx < myGUI.boardWidth(); xx++)
for (int yy = 0; yy < myGUI.boardHeight(); yy++)
currBoard.modify(xx, yy, myGUI.colorAt(xx, myGUI.boardHeight() - yy - 1));
return chooseMove();
}
// PRIVATE METHODS
/**
* Chooses the next move to make.
*
* @return the move chosen, in Board coordinates
*/
private Board.Pos chooseMove() {
// greedy choice
int max = 0; // the maximum number of points for the best position found
Board.Pos bestPos = new Board.Pos(0, 0); // the best position found
Board currStateCopy = new Board(currBoard);
for (int xx = 0; xx < currBoard.columns(); xx++)
for (int yy = 0; yy < currBoard.rows(xx); yy++) {
if (currStateCopy.getAt(xx, yy) != CircleColor.NONE) {
Board test = new Board(currStateCopy);
// mark all other nodes in the region as "clear" (but does not delete anything)
currStateCopy.clickNodeHelper(xx, yy, test.getAt(xx, yy));
// try removing the region to see what is left over
int count = test.clickNode(xx, yy);
if (count > max) {
// record a new best move
max = count;
bestPos = new Board.Pos(xx, yy);
}
}
}
// convert bestPos to GUI coordinates
bestPos = new Board.Pos(bestPos.xx, myGUI.boardHeight() - 1 - bestPos.yy);
// return the result to the GUI
return bestPos;
}
}
package edu.bu.ec504.spr24.brain;
import edu.bu.ec504.spr24.brain.Board.Pos;
/**
* A very simple Brain for the game.
*/
public class SimpleBrain extends Brain {
public SimpleBrain() {
super();
}
/**
* {@inheritDoc}
*/
public String myName() {
return "Simple Brain";
}
@Override
Pos nextMove() {
return new Pos(0, myGUI.boardHeight() - 1);
}
}
package edu.bu.ec504.spr19.Brain; package edu.bu.ec504.spr24.brain;
import edu.bu.ec504.spr19.sameGameTris.GUI;
import edu.bu.ec504.spr24.brain.Board.Pos;
import javax.swing.*; import javax.swing.*;
/** /**
* A smarter brain, for you to produce. * A smarter brain, for you to produce.
*/ */
public class smarterBrain extends Brain { public class SmarterBrain extends Brain {
public smarterBrain(GUI myGUI) { public SmarterBrain() {
super(myGUI); super();
}
@Override
public void allDone() {
} }
@Override @Override
...@@ -22,7 +17,9 @@ public class smarterBrain extends Brain { ...@@ -22,7 +17,9 @@ public class smarterBrain extends Brain {
} }
@Override @Override
public void run() { Pos nextMove() {
JOptionPane.showMessageDialog(null, "Please insert brain here!"); JOptionPane.showMessageDialog(null, "Please insert brain here!");
return null;
} }
} }
package edu.bu.ec504.spr19.highScores; package edu.bu.ec504.spr24.highScores;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
...@@ -25,8 +25,8 @@ public class highScore { ...@@ -25,8 +25,8 @@ public class highScore {
/* /*
* where the high score database is kept * where the high score database is kept
*/ */
private final String dataFile; private final String _dataFile;
private final TreeSet<HSdata> db; private final TreeSet<HSdata> _db;
// SUBCLASSES // SUBCLASSES
/** /**
...@@ -72,23 +72,23 @@ public class highScore { ...@@ -72,23 +72,23 @@ public class highScore {
* @param fileName The name of the file to use for high score information. * @param fileName The name of the file to use for high score information.
*/ */
public highScore(String fileName) { public highScore(String fileName) {
dataFile = fileName; _dataFile = fileName;
db = new TreeSet<>(new hsComp()); _db = new TreeSet<>(new hsComp());
loadScores(); _loadScores();
} }
/** /**
* Save one score to the database. * Save one score to the database.
* As a cleanup step, this method also removes the lowest scores until * As a cleanup step, this method also removes the lowest scores until
* there are at most {@code maxScores} scores in the database.. * there are at most {@code maxScores} scores in the database.
*/ */
public boolean putScore(HSdata foo) { public boolean putScore(HSdata foo) {
if (newRecordQ(foo.score)) { if (newRecordQ(foo.score)) {
db.add(foo); _db.add(foo);
while (db.size()>maxScores) while (_db.size()>maxScores)
db.remove(db.last()); _db.remove(_db.last());
saveScores(); _saveScores();
return true; return true;
} }
else else
...@@ -100,10 +100,10 @@ public class highScore { ...@@ -100,10 +100,10 @@ public class highScore {
* @return true iff score is a new record on the high score list. * @return true iff score is a new record on the high score list.
*/ */
public boolean newRecordQ(int score) { public boolean newRecordQ(int score) {
if (db.isEmpty()) // nothing in the db so far if (_db.isEmpty()) // nothing in the db so far
return true; return true;
else else
return (db.size()<maxScores-1 || score > db.last().score); return (_db.size()<maxScores-1 || score > _db.last().score);
} }
...@@ -112,7 +112,7 @@ public class highScore { ...@@ -112,7 +112,7 @@ public class highScore {
*/ */
public String display() { public String display() {
StringBuilder temp= new StringBuilder(); StringBuilder temp= new StringBuilder();
for (HSdata foo: db) { for (HSdata foo: _db) {
temp.append(hsComp.weightedScore(foo)).append(" [weighted]: ").append(foo.score).append("[actual] (").append(foo.name).append(" on ").append(foo.boardSizeX).append("x").append(foo.boardSizeY).append(")\n"); temp.append(hsComp.weightedScore(foo)).append(" [weighted]: ").append(foo.score).append("[actual] (").append(foo.name).append(" on ").append(foo.boardSizeX).append("x").append(foo.boardSizeY).append(")\n");
} }
...@@ -123,18 +123,18 @@ public class highScore { ...@@ -123,18 +123,18 @@ public class highScore {
/** /**
* Load high scores from the associated file. * Load high scores from the associated file.
*/ */
private void loadScores() { private void _loadScores() {
FileInputStream fis; FileInputStream fis;
try { try {
fis = new FileInputStream(dataFile); fis = new FileInputStream(_dataFile);
DataInputStream in = new DataInputStream(fis); DataInputStream in = new DataInputStream(fis);
BufferedReader br = new BufferedReader(new InputStreamReader(in)); BufferedReader br = new BufferedReader(new InputStreamReader(in));
String strLine; String strLine;
while ((strLine = br.readLine()) != null) { while ((strLine = br.readLine()) != null) {
String[] res = strLine.split("\t"); String[] res = strLine.split("\t");
HSdata temp = new HSdata(res[0],new Integer(res[1]),new Integer(res[2]), new Integer(res[3])); HSdata temp = new HSdata(res[0],Integer.parseInt(res[1]), Integer.parseInt(res[2]), Integer.parseInt(res[3]));
db.add(temp); _db.add(temp);
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
System.err.println("High score file not found - Starting fresh!"); System.err.println("High score file not found - Starting fresh!");
...@@ -146,14 +146,14 @@ public class highScore { ...@@ -146,14 +146,14 @@ public class highScore {
/** /**
* Save current database to the associated file. * Save current database to the associated file.
*/ */
private void saveScores() { private void _saveScores() {
FileOutputStream fos; FileOutputStream fos;
try { try {
fos = new FileOutputStream(dataFile); fos = new FileOutputStream(_dataFile);
DataOutputStream out = new DataOutputStream(fos); DataOutputStream out = new DataOutputStream(fos);
BufferedWriter br = new BufferedWriter(new OutputStreamWriter(out)); BufferedWriter br = new BufferedWriter(new OutputStreamWriter(out));
for (HSdata foo: db) { for (HSdata foo: _db) {
br.write(foo.name+"\t"+foo.boardSizeX+"\t"+foo.boardSizeY+"\t"+foo.score+"\n"); br.write(foo.name+"\t"+foo.boardSizeX+"\t"+foo.boardSizeY+"\t"+foo.score+"\n");
} }
......
package edu.bu.ec504.spr19.sameGameTris; package edu.bu.ec504.spr24.sameGameTris;
import java.awt.Color; import java.awt.Color;
import static edu.bu.ec504.spr19.sameGameTris.sge.randGen; import static edu.bu.ec504.spr24.sameGameTris.SameGameTris.randGen;
/* /*
* Records the color of one circle * Records the color of one circle
...@@ -11,32 +11,32 @@ import static edu.bu.ec504.spr19.sameGameTris.sge.randGen; ...@@ -11,32 +11,32 @@ import static edu.bu.ec504.spr19.sameGameTris.sge.randGen;
public enum CircleColor { public enum CircleColor {
NONE(null), NONE(null),
Red(new Color(255, 0, 0)), Red(new Color(255, 0, 0)),
Green(new Color(0, 255, 0)), Green(new Color(0, 255, 0)),
Blue(new Color(0, 0, 255)), Blue(new Color(0, 0, 255)),
Pink(new Color(255,0,255)); Pink(new Color(255, 0, 255));
// fields // FIELDS
private final Color myColor; private final Color _myColor;
// Constructor // CONSTRUCTOR
CircleColor(Color myColor) { CircleColor(Color myColor) {
this.myColor = myColor; this._myColor = myColor;
} }
// Accessors // ACCESSOR
Color getColor() { Color getColor() {
return myColor; return _myColor;
} }
/* /*
* Generate a random color from the first num possible colors (excluding NONE) * Generate a random color from the first num possible colors (excluding NONE)
*/ */
static CircleColor randColor() { static CircleColor randColor() {
if (sge.numColors < 1 || sge.numColors >= CircleColor.values().length) if (SameGameTris.NUM_COLORS < 1 || SameGameTris.NUM_COLORS >= CircleColor.values().length)
throw new IndexOutOfBoundsException("Only " + CircleColor.values().length + " colors are available. You requested choosing one of " + sge.numColors + " colors."); throw new IndexOutOfBoundsException("Only " + CircleColor.values().length
+ " colors are available. You requested choosing one of " + SameGameTris.NUM_COLORS + " colors.");
int randNum = 1 + Math.abs(randGen.nextInt()) % sge.numColors; int randNum = 1 + Math.abs(randGen.nextInt()) % SameGameTris.NUM_COLORS;
return CircleColor.values()[randNum]; return CircleColor.values()[randNum];
} }
} }
package edu.bu.ec504.spr19.sameGameTris; package edu.bu.ec504.spr24.sameGameTris;
import java.io.Serial;
import java.util.EventObject; import java.util.EventObject;
/* /*
...@@ -8,35 +9,40 @@ import java.util.EventObject; ...@@ -8,35 +9,40 @@ import java.util.EventObject;
class CircleRolloverEvent extends EventObject { class CircleRolloverEvent extends EventObject {
// Fields // Fields
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/* /*
* the location of the button broadcasting the event * the location of the button broadcasting the event
*/ */
private final int xx; private final int _xx;
private final int yy; private final int _yy;
private final CircleColor clr; private final CircleColor clr;
private final SelfAwareCircle.Visibility visibility; private final SelfAwareCircle.Visibility visibility;
// Constructor(s) // Constructor(s)
/* /*
* Records a rollover event on a specific button * Records a rollover event on a specific button
* @param source The "source" upon which the event initially occurred
* @param xx The x coordinate (in the gridlayout of buttons) of the button that was rolled over * @param xx The x coordinate (in the gridlayout of buttons) of the button that was rolled over
* @param yy The y coordinate (in the gridlayout of buttons) of the button that was rolled over * @param yy The y coordinate (in the gridlayout of buttons) of the button that was rolled over
* @param clr The color of the button that initiated the event * @param clr The color of the button that initiated the event
* @param visibility Should circles be highlighted or unhighlighted * @param vis Should circles be highlighted or unhighlighted
* @param source The "source" upon which the event initially occured
*/ */
public CircleRolloverEvent(Object source, int xx, int yy, CircleColor clr, SelfAwareCircle.Visibility toDo) { public CircleRolloverEvent(Object source, int xx, int yy, CircleColor clr,
SelfAwareCircle.Visibility vis) {
super(source); // register with the superclass super(source); // register with the superclass
this.xx=xx; this.yy=yy; this.clr=clr; this.visibility = toDo; this._xx = xx;
this._yy = yy;
this.clr = clr;
this.visibility = vis;
} }
// Methods
// ... accessor
int getXX() { return xx; }
int getYY() { return yy; }
SelfAwareCircle.Visibility getAction() { return visibility; }
CircleColor getColor() { return clr; }
// ACCESSORS
int getXX() {return _xx;}
int getYY() {return _yy;}
SelfAwareCircle.Visibility getVisibility() {return visibility;}
CircleColor getColor() {return clr;}
} }
package edu.bu.ec504.spr19.sameGameTris; package edu.bu.ec504.spr24.sameGameTris;
import java.io.Serial;
import javax.swing.*; import javax.swing.*;
/** /**
...@@ -7,10 +8,18 @@ import javax.swing.*; ...@@ -7,10 +8,18 @@ import javax.swing.*;
* It contains all the interfaces your Brain might need to make its moves. * It contains all the interfaces your Brain might need to make its moves.
*/ */
public abstract class GUI extends JFrame { public abstract class GUI extends JFrame {
/**
* A number used for serialization.
*/
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
GUI(String s) { /**
super(s); * @param title Creates a new GUI with the given title.
*/
GUI(String title) {
super(title);
} }
/* /*
...@@ -37,7 +46,7 @@ public abstract class GUI extends JFrame { ...@@ -37,7 +46,7 @@ public abstract class GUI extends JFrame {
/** /**
* "Clicks" on the circle at location ({@code xx},{@code yy}). * "Clicks" on the circle at location ({@code xx},{@code yy}).
* * <p>
* When your Brain wishes to make a move, it should call this method with the location of the * When your Brain wishes to make a move, it should call this method with the location of the
* circle that it wishes to select. * circle that it wishes to select.
* @param xx The x-coordinate of the circle on which to click, within the grid of circles. * @param xx The x-coordinate of the circle on which to click, within the grid of circles.
......
package edu.bu.ec504.spr24.sameGameTris;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.*;
import java.io.Serial;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.ButtonGroup;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import edu.bu.ec504.spr24.brain.SmarterBrain;
import edu.bu.ec504.spr24.highScores.highScore;
import edu.bu.ec504.spr24.brain.Brain;
import edu.bu.ec504.spr24.brain.LazyBrain;
import edu.bu.ec504.spr24.brain.SimpleBrain;
import static java.util.concurrent.Executors.newScheduledThreadPool;
/**
* A Singleton Graphical User Interface for the game.
* @author Ari Trachtenberg
*/
public class SameGameTris extends GUI implements ActionListener, ItemListener {
// CONSTANTS
static public final int DEFAULT_WIDTH = 15, DEFAULT_HEIGHT = 10; // sizes in terms of numbers of circles
/**
* Rate at which new "Tetrising" circles are being produced, in microseconds.
* Production rate increases invesely proportional to the number of circle selections clicked so far.
*/
static private long PRODUCT_CIRCLE_US = 10000000;
/**
* Rate at which new "Tetrising" circles descend on the board, in microseconds.
* Production rate increases invesely proportional to the number of circle selections clicked so far.
*/
static private long DROP_CIRCLE_US = 5000000;
/**
* The number of microseconds between Brain moves
*/
private static final long BRAIN_WAIT_US = 1000000;
/**
* The amount of time to wait between games, to allow for existing elements to settle.
*/
private static final long TIME_BETWEEN_GAMES_MS = 1000;
/**
* The number of empty rows on the top of the game.
*/
static public final int DEFAULT_EMPTY_ROWS = DEFAULT_HEIGHT / 3;
/**
* The default window size, if run as a standalone application.
*/
static public final int DEFAULT_WINDOW_WIDTH = 500, DEFAULT_WINDOW_HEIGHT = 300;
/**
* The number of colors available for circles.
*/
static public final int NUM_COLORS = 3;
/**
* The number of milliseconds to wait after selecting a region before disappearing it.
* This allows the user to see the region being selected
*/
static private final int DISPLAY_WAIT_MS = 700;
/**
* The file where high scores are stored.
*/
static private final String HIGH_SCORE_FILE = ".highscores.db";
@Serial
static private final long serialVersionUID = 1L;
/**
* Keeps track of the number of points for the selected region.
*/
final JLabel regionPoints = new JLabel("0");
/**
* Keeps track of the total number of points so far
*/
final JLabel totalPoints = new JLabel("0");
// FIELDS
private int width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT; // initial width and height of the array of circles
private final int emptyRows = DEFAULT_EMPTY_ROWS; // number of rows on top of screen with no circles
private SelfAwareCircle[][] circles;
private String highScoreName = null; // the name to use for the high score
static final Random randGen = new Random(); // package-private random number generator used for the entire package (i.e., repeatedly seeding with the same value produces the same randomness).
/**
* Counts the number of clicks on circles since the last time the board was reset.
* @requires must be a positive integer; c
*/
static private int numClicks = 1;
/**
* true iff the game is over (i.e., not running)
*/
boolean gameOver = false;
/**
* An instance of the SGE GUI.
*/
private static SameGameTris Instance = null;
// ... GUI elements
private JPanel circlePanel; // the panel containing the circles
private JPanel textPanel; // the panel containing scoring information
// ... ... menu items
private JMenuItem changeBoard;
private JMenuItem quitGame;
private JRadioButtonMenuItem noPlayerMenuItem, simplePlayerMenuItem, lazyPlayerMenuItem, // various automatic player options
smarterPlayerMenuItem;
private final ReentrantLock GUIlock = new ReentrantLock(); // thread lock used to synchronize manipulation of circles on the board
// ... Brain elements
Brain theBrain; // the Brain (automated player) for the game
Thread brainThread; // the thread that will run the Brain
final ScheduledExecutorService brainExec = newScheduledThreadPool(2); // set up the brain threads
private ScheduledFuture<?> brainFuture = null; // schedule for Brain events (i.e., make moves)
// ... Tetris executor
final ScheduledExecutorService tetrisExec = newScheduledThreadPool(2); // sets up tetris threads
private ScheduledFuture<?> produceCircleFuture = null; // schedule for producing circles for the app
private ScheduledFuture<?> dropCircleFuture = null; // schedule for dropping circles for the app
// SUBCLASSES
/**
* Drops a new circle at the top of the board every time it is run.
* If this is not possible, then the gameOverSignal is set to true
*/
private class initDropCircles implements Runnable {
@Override
public void run() {
GUIlock.lock(); // lock circle manipulations down
try {
int randColumn = randGen.nextInt(width);
SelfAwareCircle myCircle = circles[randColumn][0];
if (myCircle.get_state()
!= SelfAwareCircle.Visibility.clear) { // i.e. the spot is already occupied
doGameOver("overran column " + randColumn);
} else {
myCircle.resetColor();
myCircle.set_state(SelfAwareCircle.Visibility.plain);
}
} finally {
GUIlock.unlock();
}
repaint();
}
}
/**
* Moves circles down until they are connected to the main collection of circles.
*/
private class moveDropCircles implements Runnable {
@Override
public void run() {
GUIlock.lock(); // lock circle manipulations down
try {
// hunt for a circle that is above a clear circle
for (int xx = 0; xx < width; xx++)
for (int yy = height - 2; yy >= 0;
yy--) { // go down to up, so as not to move circles more than once
SelfAwareCircle orig = circles[xx][yy],
dest = circles[xx][yy + 1];
if (orig.get_state() != SelfAwareCircle.Visibility.clear && // a colored circle
dest.get_state() == SelfAwareCircle.Visibility.clear) { // above a cleared circle
// move it down
moveCircle(orig, dest);
}
}
} finally {
GUIlock.unlock();
}
repaint();
}
}
// METHODS
/*
* Default no-args constructor
*/
private SameGameTris() {
super("Ari's samegame");
}
/**
* Fire up the GUI.
*/
private void run() {
try {
SwingUtilities.invokeAndWait(
this::setupGUI
);
} catch (Exception e) {
System.out.println("Saw exception " + e);
}
}
public static SameGameTris getInstance() {
if (SameGameTris.Instance == null) {
// no Instance has been set up yet ... make it happen.
SameGameTris.Instance = new SameGameTris();
SameGameTris.Instance.run();
}
return Instance;
}
/*
* Returns the color of the circle at location [xx][yy], or NONE if the circle has been cleared
* @param xx must be between 0 and width
* @param yy must be between 0 and height
*/
public CircleColor colorAt(int xx, int yy) {
if (circles[xx][yy].isCleared())
return CircleColor.NONE;
else
return circles[xx][yy].clr;
}
/*
* Returns the width of the current board
*/
public int boardWidth() {
return width;
}
/*
* Returns the height of the current board
*/
public int boardHeight() {
return height;
}
/*
* Returns true iff the game is over
* (i.e. every circle is surrounded by cleared circles or circles of a different color)
*/
public boolean gameOverQ() {
if (gameOver)
return true;
for (int xx = 0; xx < width; xx++)
for (int yy = 0; yy < height; yy++) {
if (!circles[xx][yy].isCleared()) {
CircleColor myColor = circles[xx][yy].getColor();
// check its neighbors
if (sameColor(myColor, xx - 1, yy) ||
sameColor(myColor, xx + 1, yy) ||
sameColor(myColor, xx, yy - 1) ||
sameColor(myColor, xx, yy + 1))
return false; // the game has not ended
}
}
return true; // there are no viable moves
}
/*
* "Clicks" on the circle at location (xx,yy)
*/
@Override
public void makeMove(int xx, int yy) {
circles[xx][yy].mouseEntered(null); // pretend the mouse was pressed at location (xx,yy)
Timer t = new Timer();
TimerTask tt = new TimerTask() {
@Override
public void run() {
final SelfAwareCircle theCircle = circles[xx][yy];
theCircle.mouseExited(null);
theCircle.mousePressed(null);
theCircle.mouseReleased(
null); // pretend that the mouse button was released at the location
}
};
t.schedule(tt, DISPLAY_WAIT_MS);
}
/**
* @return the score achieved from clicking on a region of length <b>level</b>
*/
@Override
final public int score(int level, CircleColor clr) {
if (level == 1)
return 1;
else
return level * level;
}
// OTHER METHODS
/*
* setupGUI helper
* - adds to circle[ii][jj] a SelfAwareListener of circle[xx][yy] as 0<=xx<width and 0<=yy<height
* @param ii = x coordinate of the circle to whom we will be listening
* @param jj = y coordinate of the circle to whom we will be listening
* @param xx = x coordinate of the listener circle
* @param yy = y coordinate of the listener circle
*/
private void addNeighbor(int ii, int jj, int xx, int yy) {
if ((0 <= xx && xx < width) &&
(0 <= yy && yy < height))
circles[ii][jj].addSelfAwareListener(circles[xx][yy]);
}
/**
* Set up the initial screen items
* NOTE: must perform cleanUp() first if there is already a GUI set up on the screen
*/
void setupGUI() {
// SET LAYOUT STYLE
setLayout(new BorderLayout());
// SET UP MENUS
JMenuBar menuBar = new JMenuBar();
// ... File Menu
JMenu menu = new JMenu("File");
changeBoard = new JMenuItem("Change board");
changeBoard.addActionListener(this);
menu.add(changeBoard);
quitGame = new JMenuItem("Quit");
quitGame.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.META_DOWN_MASK));
quitGame.setMnemonic('Q');
quitGame.addActionListener(this);
menu.add(quitGame);
menuBar.add(menu);
// ... Automatic play Menu
menu = new JMenu("Automatic play");
ButtonGroup group = new ButtonGroup();
noPlayerMenuItem = new JRadioButtonMenuItem("none");
noPlayerMenuItem.addItemListener(this);
noPlayerMenuItem.setSelected(true); // i.e. the default item
group.add(noPlayerMenuItem);
menu.add(noPlayerMenuItem);
menu.addSeparator();
simplePlayerMenuItem = new JRadioButtonMenuItem("Simple player");
simplePlayerMenuItem.addItemListener(this);
group.add(simplePlayerMenuItem);
menu.add(simplePlayerMenuItem);
lazyPlayerMenuItem = new JRadioButtonMenuItem("Lazy player");
lazyPlayerMenuItem.addItemListener(this);
group.add(lazyPlayerMenuItem);
menu.add(lazyPlayerMenuItem);
smarterPlayerMenuItem = new JRadioButtonMenuItem("Smarter player");
smarterPlayerMenuItem.addItemListener(this);
group.add(smarterPlayerMenuItem);
menu.add(smarterPlayerMenuItem);
menuBar.add(menu);
this.setJMenuBar(menuBar);
// SET UP CIRCLE PANEL
circlePanel = new JPanel(new GridLayout(height, width));
// ... allocate circles
circles = new SelfAwareCircle[width][height];
// ..... set up some circles
for (int jj = 0; jj < height; jj++)
for (int ii = 0; ii < width; ii++) {
// establish the button
if (jj <= emptyRows) {
circles[ii][jj] = new SelfAwareCircle(CircleColor.NONE, ii,
jj); // the top emptyRows should have a circle with no color
}
else {
circles[ii][jj] = new SelfAwareCircle(ii, jj);
}
circlePanel.add(circles[ii][jj]);
}
// ... set up listeners in the area
for (int xx = 0; xx < width; xx++)
for (int yy = 0; yy < height; yy++) {
// add all neighbors
addNeighbor(xx, yy, xx, yy - 1);
addNeighbor(xx, yy, xx, yy + 1);
addNeighbor(xx, yy, xx - 1, yy);
addNeighbor(xx, yy, xx + 1, yy);
}
add(circlePanel, BorderLayout.CENTER);
// ... set up the text panel
textPanel = new JPanel(new FlowLayout());
textPanel.add(new JLabel("Selected len: "));
textPanel.add(regionPoints);
textPanel.add(new JLabel("")); // i.e. blank
textPanel.add(new JLabel("Total pts: "));
textPanel.add(totalPoints);
add(textPanel, BorderLayout.SOUTH);
// paint the display
validate();
repaint();
}
/**
* Increment the number of clicks seen.
* If this ends up being greater than the size of the board, the game is declared over.
*/
public void updateNumClicks() {
numClicks++;
if (numClicks > width * height)
doGameOver("too many clicks - " + numClicks);
}
/**
* Cancel schedules for adding / dropping circles.
*/
private void cancelTetrisingSchedules() {
if (produceCircleFuture != null)
produceCircleFuture.cancel(true);
if (dropCircleFuture != null)
dropCircleFuture.cancel(true);
}
/**
* @return true iff tetrising is going on
*/
public boolean isTetrisingQ() {
return produceCircleFuture!=null && produceCircleFuture.isCancelled()==false;
}
/**
* Sets up threads that drop circles onto the board and move them down over time.
* As {@link #numClicks} gets larger, circles come more often and drop faster.
*/
public void restartTetrising() {
// cancel the old schedules
cancelTetrisingSchedules();
// start up the new ones
produceCircleFuture = tetrisExec.scheduleAtFixedRate(new initDropCircles(), 0,
PRODUCT_CIRCLE_US / numClicks, TimeUnit.MICROSECONDS);
dropCircleFuture = tetrisExec.scheduleAtFixedRate(new moveDropCircles(), 0, DROP_CIRCLE_US / numClicks,
TimeUnit.MICROSECONDS);
}
/*
* Performs any clean up actions needed before setting up a new GUI
*/
void cleanUp() {
// Removes elements for garbage collection
// close down the brain
highScoreName = null;
stopBrain();
// score panel
totalPoints.setText("0");
textPanel.setVisible(false);
textPanel = null;
// circles
for (int ii = 0; ii < width; ii++)
for (int jj = 0; jj < height; jj++) {
circles[ii][jj].removeMouseListener(circles[ii][jj]);
circles[ii][jj] = null;
}
// circle panel
circlePanel.setVisible(false);
circlePanel = null;
// cancel tetrising schedules
numClicks = 1; // reset the number of circle clicks in the system
cancelTetrisingSchedules();
// reset gameOver
gameOver = false;
}
/*
* Returns true iff circles[xx][yy] has color theColor, is not cleared, AND (xx,yy) is within the range of the board
*/
private boolean sameColor(CircleColor theColor, int xx, int yy) {
if (xx < 0 || yy < 0 || xx >= width || yy >= height || circles[xx][yy].isCleared())
return false;
else
return circles[xx][yy].getColor().equals(theColor);
}
/*
* Moves circle c1 into location c2, leaving c1 as a clear circle that
* does not receive mouse events
*/
synchronized private void moveCircle(SelfAwareCircle orig, SelfAwareCircle dest) {
// copy the immutable, position independent values
orig.copyTo(dest);
// clear the top item
orig.setClear();
}
/*
* Called to request a reshifting of the board (as necessary).
* This should happen if some circles are rendered "clear"ed
*/
final void shiftCircles() {
/* start at the bottom and move up ... all cleared circles are
* removed, with upper circles falling into their positions;
* if a column is totally empty, then its rightmost columns
* shift into it
*/
GUIlock.lock(); // acquire a lock
try {
// // 1. SHIFT VERTICALLY
// for (int xx = 0; xx < width; xx++) {
// int firstClr = height - 1; // the lowest cleared entry in the column
// int firstFull =
// height - 1; // the lowest uncleared entry in the column that has not yet been processed
// boolean moveOn = false; // set to true in order to move on to the next column
// while (!moveOn) {
// // find the lowest clear entry in the column (if it exists)
// try {
// while (!circles[xx][firstClr].isCleared())
// firstClr--;
// } catch (ArrayIndexOutOfBoundsException e) {
// moveOn = true;
// continue; // i.e. no cleared circle found in this column --- go to the next column
// }
//
// if (firstFull > firstClr)
// firstFull = firstClr; // only move items "down" the column
//
// // find the lowest non-cleared entry in the column (if it exists)
// try {
// while (circles[xx][firstFull].isCleared())
// firstFull--;
// } catch (ArrayIndexOutOfBoundsException e) {
// moveOn = true;
// continue; // i.e. the whole column is clear --- for now, go to the next column
// }
//
// moveCircle(circles[xx][firstFull], circles[xx][firstClr]);
//
// firstFull--;
// firstClr--; // iterate
// }
// }
// 2. SHIFT HORIZONTALLY
// Check to see if any column is now empty
// ... this could have been done within the loop above, but it would detract from readability of the code
boolean emptySoFar = true; // remains true if all columns seen so far have only cleared circles
for (int xx = width - 1; xx >= 0; xx--) {
boolean allCleared = true; // remains true if all circles in column xx have been cleared
for (int yy = 0; yy < height; yy++)
if (!circles[xx][yy].isCleared()) {
allCleared = false;
break;
}
if (allCleared) {
if (!emptySoFar) { // i.e. do not do anything with empty columns on the right of the screen
// move other columns into this empty column
for (int ii = xx + 1; ii < width; ii++)
for (int jj = 0; jj < height; jj++)
moveCircle(circles[ii][jj], circles[ii - 1][jj]);
}
} else
emptySoFar = false;
}
// 3. (LAST ITEM) CHECK IF THE GAME HAS ENDED
// This happens if every circle is surrounded by cleared circles or circles of a different color
if (gameOverQ()) {
doGameOver("exhausted all circles"); // all done
}
} finally {
GUIlock.unlock(); // release the circles for other processes
}
}
/**
* The game is over - report to the user and/or save a high score, if relevant.
*
* @param feedback Some string to present to the user; typically the reason that the game is over.
*/
private void doGameOver(String feedback) {
// close out other threads and variables
gameOver = true;
cancelTetrisingSchedules(); // cancel Tetrising threads
stopBrain(); // stop the thinking Brain
// check the high score
int score = Integer.parseInt(totalPoints.getText());
highScore hs = new highScore(HIGH_SCORE_FILE);
if (hs.newRecordQ(score)) { // i.e. a new record
_showMessage(null,
"Game Over (" + feedback + ") - You got a new high score of " + score + " points!\n" +
"Your weighted score for this sized board was " + highScore.hsComp.weightedScore(
new highScore.HSdata("", width, height, score)) + ".");
if (highScoreName == null)
highScoreName = _showInput(null,
"You got a new high score!\nPlease enter your name:");
if (highScoreName != null) { // i.e. the user is interested in high scores
// populate the high score item
highScore.HSdata datum = new highScore.HSdata(highScoreName, width, height, score);
hs.putScore(datum); // enter the name into the high score list
}
} else {
_showMessage(null,
"Game Over (" + feedback + ") - You did not make the high score. You had " + score
+ " points.\n" +
"Your weighted score for this sized board was " + highScore.hsComp.weightedScore(
new highScore.HSdata("", width, height, score)) + ".");
}
_showMessage(null, "Current high scores:\n" + hs.display());
// // cleanup
// cleanUp();
// setupGUI();
// "that's all folks"
System.exit(0);
}
/**
* Do a task in the future.
* @param task The task to complete.
* @param whenMS How many milliseconds in the future to do the task.
*/
private void _doInFuture(TimerTask task, long whenMS) {
Timer timer = new Timer();
timer.schedule(task, whenMS);
}
/**
* Shows a message on the parent component and waits for the user to click "OK".
* @param parent The component on which to show the dialog box.
* @param message The message to display.
*/
private void _showMessage(Component parent, String message) {
JOptionPane.showMessageDialog(parent, message);
}
/**
* Shows an input request on the parent component and returns the user's input.
* @param parent The component on which to show the dialog box.
* @param message The input request
*/
private String _showInput(Component parent, String message) {
return JOptionPane.showInputDialog(parent, message);
}
/**
* Handle menu events
* @param e The event to handle.
*/
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source == quitGame)
System.exit(0);
else if (source == changeBoard) {
// modified from http://www.vbforums.com/showthread.php?t=513699
JTextField width = new JTextField();
width.setText("" + DEFAULT_WIDTH);
JTextField height = new JTextField();
height.setText("" + DEFAULT_HEIGHT);
Object[] msg = {"Width:", width, "Height:", height};
JOptionPane op = new JOptionPane(
msg,
JOptionPane.QUESTION_MESSAGE,
JOptionPane.OK_CANCEL_OPTION,
null,
null);
JDialog dialog = op.createDialog(this, "Enter new board size ...");
dialog.setVisible(true);
int result = JOptionPane.OK_OPTION;
try {
result = (Integer) op.getValue();
} catch (Exception ignored) {
}
if (result == JOptionPane.OK_OPTION) // i.e. effect the change
{
// i.e. destroy the old board
cleanUp();
this.width = Integer.parseInt(width.getText());
this.height = Integer.parseInt(height.getText());
// create the new board
setupGUI();
}
}
}
/**
* Starts a Brain that makes moves
*/
private void startBrain(Brain theBrain) {
this.theBrain = theBrain;
highScoreName = theBrain.myName(); // the computer gets credit for any subsequent high score
// brainThread = new Thread(theBrain, "Brain Thread");
// brainThread.start();
brainFuture = brainExec.scheduleAtFixedRate(theBrain, 0, BRAIN_WAIT_US, TimeUnit.MICROSECONDS);
}
/**
* Stops the Brain that makes move.
*/
private void stopBrain() {
if (theBrain != null) {
theBrain.allDone();
brainFuture.cancel(true);
}
}
/**
* Handles the menu checkBox
*/
public void itemStateChanged(ItemEvent e) {
Object source = e.getItemSelectable();
if (e.getStateChange() == ItemEvent.SELECTED) {
if (source == noPlayerMenuItem)
return; // i.e. no players
else if (source == simplePlayerMenuItem)
startBrain(new SimpleBrain());
else if (source == lazyPlayerMenuItem)
startBrain(new LazyBrain());
else if (source == smarterPlayerMenuItem)
startBrain(new SmarterBrain());
} else {
// deselected
if (theBrain != null)
theBrain.allDone(); // will ask the Brain to stop thinking
}
}
// MAIN METHOD
/**
* Runs the application
*/
public static void main(String[] args) {
JFrame myApp = SameGameTris.getInstance();
myApp.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myApp.setSize(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT);
myApp.setVisible(true);
}
}
package edu.bu.ec504.spr24.sameGameTris;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.Serial;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/*
* An extension of JButton that keeps track of its own location on a grid
*/
class SelfAwareCircle extends Component implements MouseListener, SelfAwareListener {
// Constants
private final Color _hltColor = new Color(200, 200, 200); // the color to use for highlighting a button
// FIELDS
public final int xx;
public final int yy;
public CircleColor clr;
@Serial
private static final long serialVersionUID = 1L;
private final ArrayList<SelfAwareListener> _listeners = new ArrayList<>(); // who is listening to events fired by this circle
private static int _id = 0;
private final int _myID; // the circle's ID
private Visibility _state = Visibility.plain; // the current state of the circle
private final SameGameTris sameGameTrisGUI; // a link to the GUI that created this circle
/**
* Used to manage concurrency.
*/
private static final AtomicInteger regionLength = new AtomicInteger(0);
// SUBCLASSES
/*
* Records the visibility of an object.
*/
public enum Visibility {
/*
* the circle is highlighted
*/
highlight,
/*
* the circle is plain (i.e. the default)
*/
plain,
/*
* the circle is cleared (i.e. background color)
*/
clear
}
// CONSTRUCTORS
/**
* Creates a circle of the specified color at the specified location
*
* @param clr The color of the new circle.
* @param xx The xx location of the circle in the grid of circles.
* @param yy The yy location of the circle in the grid of circles.
*/
SelfAwareCircle(CircleColor clr, int xx, int yy) {
// record field parameters
this.xx = xx;
this.yy = yy;
this.clr = clr; // record the color of the button
if (clr == CircleColor.NONE) // circles with no color should be cleared
setClear();
this.sameGameTrisGUI = SameGameTris.getInstance(); // record the GUI that created this button
addMouseListener(this); // register for mouse events
_myID = _id++;
}
/**
* Creates a circle of the specified color at the specified location, based on {@link #SelfAwareCircle(CircleColor, int, int) .}
*/
SelfAwareCircle(int xx, int yy) {
this(CircleColor.randColor(), xx, yy);
}
// METHODS
// ... helpers
// .. info methods and accessors/setters
static int getRegionLength() {
return regionLength.get();
}
public int getId() {
return _myID;
}
Visibility get_state() {
return _state;
}
public CircleColor getColor() {
return clr;
}
/**
* Resets the color of this circle to a random color.
*/
void resetColor() {
clr = CircleColor.randColor();
}
void set_state(Visibility _state) {
if (_state == Visibility.clear)
setClear(); // i.e. there is extra overhead in clearing
else
this._state = _state;
repaint();
}
boolean isCleared() {
return this._state == Visibility.clear;
}
void setClear() {
_state = Visibility.clear;
repaint();
}
/*
* Copies immutable elements of this SelfAwareCircle to "recipient"
* *except* position-dependent items (e.g. _listeners, xx, yy)
*/
void copyTo(SelfAwareCircle recipient) {
recipient.clr = clr;
if (recipient._state == _state)
return; // i.e. nothing to do
// special cases
// ... copying a cleared circle to an uncleared circle
if (_state == Visibility.clear)
recipient.setClear(); // includes removing a mouse listener
// copy the state
recipient.set_state(_state);
}
// ... Mouse methods
public void mouseClicked(MouseEvent e) {
}
/*
* Highlights the button by changing its icon and firing off an event
* @param state determines whether to highlight or unhighlight the circle
*/
private void _highlightButton(Visibility stt) {
if (isCleared()) // don't go further with already cleared circles
return;
// set the state
if (stt == Visibility.clear) // clear has its own special treatment
setClear(); // properly clear this circle (including removal of mouse listener)
else
set_state(stt); // set the non-cleared state appropriately
// deal with the region length
regionLength.getAndIncrement(); // update the region length by the newly highlighted item
// announce the rollover to other circles
_fireButtonRolloverEvent(stt);
}
/*
* Mouse entered the object area:
* * Change its button icon
* * Broadcast the change to other buttons
* @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
*/
public void mouseEntered(MouseEvent e) {
regionLength.set(0); // i.e. reinitialize
_highlightButton(Visibility.highlight);
}
/**
* Mouse exited the object area.
* * Change its button icon
* * Broadcast the change to other buttons
*/
public void mouseExited(MouseEvent e) {
regionLength.set(0); // i.e. reinitialize
_highlightButton(Visibility.plain);
}
/**
* The mouse button was pressed.
*/
public void mousePressed(MouseEvent e) {
regionLength.set(0); // i.e. reinitialize
}
/**
* The mouse button was released.
*/
public void mouseReleased(MouseEvent e) {
_highlightButton(Visibility.clear);
// register the score
sameGameTrisGUI.totalPoints.setText("" +
(Integer.parseInt(sameGameTrisGUI.totalPoints.getText()) +
sameGameTrisGUI.score(SelfAwareCircle.getRegionLength(), getColor())));
// request that the board be shifted appropriately
sameGameTrisGUI.shiftCircles();
// update Tetrising
sameGameTrisGUI.updateNumClicks();
if (!sameGameTrisGUI.gameOverQ())
sameGameTrisGUI.restartTetrising();
}
// ... event methods
public synchronized void addSelfAwareListener(SelfAwareListener l) {
_listeners.add(l);
}
public synchronized void removeSelfAwareListener(SelfAwareListener l) {
_listeners.remove(l);
}
/*
* fires off events to highlight or unhighlight circles
* @param state determines whether to highlight or unhighlight circles
*/
private synchronized void _fireButtonRolloverEvent(SelfAwareCircle.Visibility state) {
CircleRolloverEvent bre = new CircleRolloverEvent(this, xx, yy, clr, state);
for (SelfAwareListener _listener : _listeners) {
SelfAwareCircle temp = (SelfAwareCircle) _listener;
temp.rollingOver(bre);
}
}
/*
* What to do when receiving information from another button of a rolling over event
* @param e the event broadcast by the other button
*/
public void rollingOver(CircleRolloverEvent e) {
if (_state == e.getVisibility() || isCleared()) // i.e. this circle has already been processed
return;
if (e.getColor().equals(clr)) { // check if the color is the same as mine
_highlightButton(e.getVisibility());
}
// in all other cases, only update the text field (i.e. stop the recursion)
sameGameTrisGUI.regionPoints.setText("" + (SelfAwareCircle.getRegionLength())); // update the gui text field
}
// ... drawing methods
public void paint(Graphics g) {
// set the color, depending on whether this is being highlighted
switch (_state) {
case highlight:
g.setColor(_hltColor); // change to highlighting color
break;
case plain:
g.setColor(clr.getColor()); // the default color
break;
case clear:
g.setColor(getParent().getBackground()); // i.e. set to background color
break;
}
// size of component
Dimension size = getSize();
// draw the circle
g.fillOval(0, 0, size.width, size.height);
}
}
package edu.bu.ec504.spr19.sameGameTris; package edu.bu.ec504.spr24.sameGameTris;
/* /*
* Listener for events of interest to SelfAwareJButtons * Listener for events of interest to SelfAwareJButtons
......