Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • cagnion/mp2_anahita_et_constance_2
1 result
Show changes
Commits on Source (2)
Showing
with 0 additions and 1998 deletions
# Default ignored files
/shelf/
/workspace.xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="iccoop" />
<module name="tutos" />
<module name="game-engine" />
</profile>
</annotationProcessing>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/game-engine/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/game-engine/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/iccoop/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/iccoop/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/tutos/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/tutos/src/main/resources" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/MP2" vcs="Git" />
</component>
</project>
\ No newline at end of file
MP2 @ 43c88175
Subproject commit 43c881750715e5c3a2c01d53c1467d7d5f840116
### Maven template
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
# Eclipse m2e generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
### Java template
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Eclipse template
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
.apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.epfl.cs107</groupId>
<artifactId>mp2</artifactId>
<version>2024</version>
</parent>
<groupId>ch.epfl.cs107</groupId>
<artifactId>game-engine</artifactId>
<version>1.0.0-RC5</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
\ No newline at end of file
package ch.epfl.cs107.play.areagame;
import ch.epfl.cs107.play.areagame.area.Area;
import ch.epfl.cs107.play.engine.Drawable;
import ch.epfl.cs107.play.engine.Game;
import ch.epfl.cs107.play.engine.PauseMenu;
import ch.epfl.cs107.play.io.FileSystem;
import ch.epfl.cs107.play.window.Window;
import java.util.HashMap;
import java.util.Map;
/**
* AreaGames are a concept of Game which is displayed in a (MxN) Grid which is called an Area
* An AreaGame has multiple Areas
*/
abstract public class AreaGame implements Game, PauseMenu.Pausable {
// Context objects
private Window window;
private FileSystem fileSystem;
/// A map containing all the Area of the Game
private Map<String, Area> areas;
/// The current area the game is in
private Area currentArea;
/// pause mechanics and menu to display. May be null
private boolean paused, requestPause;
private PauseMenu menu;
/**
* Add an Area to the AreaGame list
*
* @param a (Area): The area to add, not null
*/
public final void addArea(Area a) {
areas.put(a.getTitle(), a);
}
/**
* Setter for the current area: Select an Area in the list from its key
* - the area is then begin or resume depending if the area is already started or not and if it is forced
*
* @param key (String): Key of the Area to select, not null
* @param forceBegin (boolean): force the key area to call begin even if it was already started
* @return (Area): after setting it, return the new current area
*/
protected final Area setCurrentArea(String key, boolean forceBegin) {
Area newArea = areas.get(key);
if (newArea == null) {
System.out.println("New Area not found, keep previous one");
} else {
// Stop previous area if it exist
if (currentArea != null) {
currentArea.suspend();
currentArea.purgeRegistration(); // Is useful?
}
currentArea = newArea;
// Start/Resume the new one
if (forceBegin || !currentArea.isStarted()) {
currentArea.begin(window, fileSystem);
} else {
currentArea.resume(window, fileSystem);
}
}
return currentArea;
}
/**
* Set the pause menu
*
* @param menu (PauseMenu) : the new pause menu, not null
* @return (PauseMenu): the new pause menu, not null
*/
protected final PauseMenu setPauseMenu(PauseMenu menu) {
this.menu = menu;
this.menu.begin(window, fileSystem);
this.menu.setOwner(this);
return menu;
}
/**
* @return (Window) : the Graphic and Audio context
*/
protected final Window getWindow() {
return window;
}
/**
* @return (FIleSystem): the linked file system
*/
protected final FileSystem getFileSystem() {
return fileSystem;
}
/**
* Getter for the current area
*
* @return (Area)
*/
protected final Area getCurrentArea() {
return this.currentArea;
}
/// AreaGame implements Playable
@Override
public boolean begin(Window window, FileSystem fileSystem) {
// Keep context
this.window = window;
this.fileSystem = fileSystem;
areas = new HashMap<>();
paused = false;
return true;
}
@Override
public void update(float deltaTime) {
// HR : Check if pause was not requested on the previous interaction
paused = requestPause;
if (paused && menu != null) {
menu.update(deltaTime);
} else {
currentArea.update(deltaTime);
// currentArea.draw(getWindow());
}
}
@Override
public void draw() {
if (!paused || menu == null) {
currentArea.draw(getWindow());
}
}
@Override
public void end() {
// save the game states somewhere if needed
}
@Override
public void requestPause() {
requestPause = true;
}
@Override
public void requestResume() {
requestPause = false;
}
@Override
public boolean isPaused() {
return paused;
}
}
package ch.epfl.cs107.play.areagame;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import ch.epfl.cs107.play.math.Orientation;
import ch.epfl.cs107.play.math.DiscreteCoordinates;
import ch.epfl.cs107.play.signal.logic.Logic;
/**
* AreaGraph is a specific kind of graph apply to Area.
* The graph is composed of AreaNodes which are defined by their position in the graph (DiscreteCoordinates)
* and the existence of directed edge between them (from) and their four neighbors (to).
* Nodes are stored into a Map.
* Note: DiscreteCoordinate are serializable reimplementing hashCode() and equals() making the keys dependant only from
* the DiscreteCoordinate x and y values and not from the object itself.
*/
public class AreaGraph {
/// Map containing all the node or vertices of the area graph
private final Map<DiscreteCoordinates, AreaNode> nodes;
/**
* Default AreaGraph Constructor
*/
public AreaGraph(){
nodes = new HashMap<>();
}
/**
* Add if absent a new Node into the graph.
* Create a new Node and put it in the nodes map at given coordinates key.
* Note: DiscreteCoordinate are serializable reimplementing hashCode() and equals() making the keys dependant only from
* the DiscreteCoordinate x and y values and not from the object itself.
* @param coordinates (DiscreteCoordinate): Position in the graph of the node to add, used as key for the map, not null
* @param hasLeftEdge (boolean): indicate if directed edge to the left direction exists
* @param hasUpEdge (boolean): indicate if directed edge to the up direction exists
* @param hasRightEdge (boolean): indicate if directed edge to the right direction exists
* @param hasDownEdge (boolean): indicate if directed edge to the down direction exists
*/
public void addNode(DiscreteCoordinates coordinates, boolean hasLeftEdge, boolean hasUpEdge, boolean hasRightEdge, boolean hasDownEdge){
nodes.putIfAbsent(coordinates, new AreaNode(coordinates, hasLeftEdge, hasUpEdge, hasRightEdge, hasDownEdge));
}
protected Map<DiscreteCoordinates, AreaNode> getNodes() {
return nodes;
}
/**
* Return if a node exists in the graph
* @param coordinates (DiscreteCoordinates): may be null
* @return (boolean): true if the given node exists in the graph
*/
public boolean nodeExists(DiscreteCoordinates coordinates){
return nodes.containsKey(coordinates);
}
public void setSignal(DiscreteCoordinates coordinates, Logic signal) {
if(!nodes.containsKey(coordinates))
throw new IllegalArgumentException("The node do not exist");
nodes.get(coordinates).setSignal(signal);
}
protected class AreaNode{
/// Position of the node into the graph, used as key for the map
private final DiscreteCoordinates coordinates;
/// a List of the connectedNode. May be null if getConnectedNodes is never called
private List<AreaNode> connectedNodes;
/// Flag: true if a directed edge between this and indicated direction (left, up, right, down) exists
private final boolean hasLeftEdge, hasUpEdge, hasRightEdge, hasDownEdge;
// Signal indicating it the node is active
private Logic isActive;
/**
* Default AreaNode Constructor
* @param coordinates (DiscreteCoordinate): Position in the graph of the node to add, used as key for the map, not null
* @param hasLeftEdge (boolean): indicate if directed edge to the left direction exists
* @param hasUpEdge (boolean): indicate if directed edge to the up direction exists
* @param hasRightEdge (boolean): indicate if directed edge to the right direction exists
* @param hasDownEdge (boolean): indicate if directed edge to the down direction exists
*/
protected AreaNode(DiscreteCoordinates coordinates, boolean hasLeftEdge, boolean hasUpEdge, boolean hasRightEdge, boolean hasDownEdge){
this.coordinates = coordinates;
this.hasLeftEdge = hasLeftEdge;
this.hasUpEdge = hasUpEdge;
this.hasRightEdge = hasRightEdge;
this.hasDownEdge = hasDownEdge;
isActive = Logic.TRUE;
}
/**
* Neighbors getter
* see method addNeighbor()
* @return (Array of AreaNode): the array of four neighbor Nodes. Elements may be null if no connection exists
*/
List<AreaNode> getConnectedNodes() {
if(connectedNodes == null){
connectedNodes = new ArrayList<>();
addNeighbor("Left", hasLeftEdge, new DiscreteCoordinates(coordinates.x-1, coordinates.y));
addNeighbor("Up", hasUpEdge, new DiscreteCoordinates(coordinates.x, coordinates.y+1));
addNeighbor("Right", hasRightEdge, new DiscreteCoordinates(coordinates.x+1, coordinates.y));
addNeighbor("Down", hasDownEdge, new DiscreteCoordinates(coordinates.x, coordinates.y-1));
}
return connectedNodes;
}
private void addNeighbor(String neighborString, boolean hasNeighbor, DiscreteCoordinates c) {
if(hasNeighbor){
if(nodes.containsKey(c)){
connectedNodes.add(nodes.get(c));
}else{
// TODO throw exception if needed
System.out.println(neighborString + " neighbor for "+ coordinates.toString() + " Node does not exists");
}
}
}
/**
* Indicate the orientation we need to follow to reach previous node from this one
* Assume the previous node is a neighbor node
* @param previous (AreaNode), not null
* @return (Orientation)
*/
Orientation getOrientation(AreaNode previous){
if(previous.coordinates.x < coordinates.x)
return Orientation.LEFT;
if(previous.coordinates.y > coordinates.y)
return Orientation.UP;
if(previous.coordinates.x > coordinates.x)
return Orientation.RIGHT;
if(previous.coordinates.y < coordinates.y)
return Orientation.DOWN;
System.out.println("Should never print");
return null;
}
public void setSignal(Logic signal) {
isActive = signal;
}
public boolean isActive() {
return isActive.isOn();
}
}
/**
* Compute the shortest path in this AreaGraph from given DiscreteCoordinate to given DiscreteCoordinates
* @param from (DiscreteCoordinates): source node of the desired path, not null
* @param to (DiscreteCoordinates): sink node of the desired path, not null
* @return (Iterator of Orientation): return an iterator containing the shortest path from source to sink, or null if the path does not exists !
*/
public Queue<Orientation> shortestPath(DiscreteCoordinates from, DiscreteCoordinates to){
AreaNode start = nodes.get(from);
AreaNode goal = nodes.get(to);
if( goal == null || start == null || start == goal)
return null;
//System.out.println("Looking for path from: " + start.coordinates.toString() + " to : "+ goal.coordinates.toString());
// used to size data structures appropriately
final int size = nodes.size();
// Needed to see if it is already in the queue , otherwise we don't add the node
final Set<AreaNode> inQueue = new HashSet<>(size); // Changed
// The set of nodes already evaluated.
final Set<AreaNode> visitedSet = new HashSet<>(size);
// The set of tentative nodes to be evaluated, initially containing the start node
final List<AreaNode> toVisitSet = new ArrayList<>(size);
toVisitSet.add(start);
// The map of navigated nodes. <neighbor, current>
final Map<AreaNode, AreaNode> cameFrom = new HashMap<>(size);
while (!toVisitSet.isEmpty()) {
// Get the first node, we will now evaluate it
final AreaNode current = toVisitSet.get(0);
// If the current is the goal one, we can end
if (current.equals(goal))
return reconstructPath(cameFrom, goal);
// Otherwise we remove it from non-evaluated node and put it inside evaluated node
toVisitSet.remove(0);
inQueue.remove(current); // Changed
visitedSet.add(current);
// For all its neighbors
for (AreaNode neighbor : current.getConnectedNodes()) {
if (visitedSet.contains(neighbor))
// Ignore the neighbor which is already evaluated.
continue;
if(!neighbor.isActive())
// Ignore inactive neighbors
continue;
if (!inQueue.contains(neighbor))
{
toVisitSet.add(neighbor);
inQueue.add(neighbor); //Changed
}
// This path is the best. Record it!
cameFrom.put(neighbor, current);
}
}
return null;
}
private Queue<Orientation> reconstructPath(Map<AreaNode, AreaNode> cameFrom, AreaNode current) {
final List<Orientation> totalPath = new ArrayList<>();
while (current != null) {
final AreaNode previous = current;
current = cameFrom.get(current);
if (current != null) {
final Orientation edge = current.getOrientation(previous);
totalPath.add(edge);
}
}
Collections.reverse(totalPath);
/*
// Print the path for debug purpose
System.out.println("--------------------Path : ");
for(Orientation o : totalPath){
System.out.println(o.toString());
}
*/
return new LinkedList<>(totalPath);
}
}
\ No newline at end of file
package ch.epfl.cs107.play.areagame.actor;
import java.util.List;
import ch.epfl.cs107.play.engine.actor.Entity;
import ch.epfl.cs107.play.areagame.area.Area;
import ch.epfl.cs107.play.math.DiscreteCoordinates;
import ch.epfl.cs107.play.math.Orientation;
import ch.epfl.cs107.play.math.Vector;
/**
* Area Entities are assigned to at least one Area Cell which make them Interactable
*/
public abstract class AreaEntity extends Entity implements Interactable {
/// AreaEntity are disposed inside an Area
private Area ownerArea;
/// Orientation in the Area
private Orientation orientation;
/// Coordinate of the main Cell linked to the entity
private DiscreteCoordinates currentMainCellCoordinates;
/**
* Default AreaEntity constructor
* @param area (Area): Owner area. Not null
* @param orientation (Orientation): Initial orientation of the entity in the Area. Not null
* @param position (DiscreteCoordinate): Initial position of the entity in the Area. Not null
*/
public AreaEntity(Area area, Orientation orientation, DiscreteCoordinates position) {
super(position.toVector());
if(area == null){
throw new NullPointerException();
}
this.ownerArea = area;
this.orientation = orientation;
this.currentMainCellCoordinates = position;
}
/**
* Getter for the owner area
* @return (Area)
*/
protected Area getOwnerArea() {
return ownerArea;
}
/**
* Set the owner area with new value
* @param newArea (Area): the new value. Not null
*/
protected void setOwnerArea(Area newArea) {
this.ownerArea = newArea;
}
/**
* Getter for the orientation
* @return (Orientation): current orientation
*/
public Orientation getOrientation() {
return orientation;
}
/**
* Orientate the AreaEntity to a new orientation
* @param orientation (Orientation): The new orientation. Not null
* @return (boolean): if the orientation change happens, by default always true
*/
protected boolean orientate(Orientation orientation) {
this.orientation = orientation;
return true;
}
/**
* Getter for the coordinates of the main cell occupied by the AreaEntity
* @return (DiscreteCoordinates)
*/
public DiscreteCoordinates getCurrentMainCellCoordinates(){
return currentMainCellCoordinates;
}
/**
* Tell if the mouse is over any of the currentCells of the entity
* @return (boolean)
*/
protected boolean isMouseOver() {
List<DiscreteCoordinates>cells = getCurrentCells();
DiscreteCoordinates mouseCoordinate = ownerArea.getRelativeMouseCoordinates();
for(DiscreteCoordinates cell : cells) {
if(cell.equals(mouseCoordinate)) {
return true;
}
}
return false;
}
/// AreaEntity extends Entity
@Override
protected void setCurrentPosition(Vector v){
// When updating the current position, also check if we need to update the main cell coordinates
if(DiscreteCoordinates.isCoordinates(v)){
this.currentMainCellCoordinates = new DiscreteCoordinates(Math.round(v.x), Math.round(v.y));
v = v.round();
}
super.setCurrentPosition(v);
}
@Override
public void onLeaving(List<DiscreteCoordinates> coordinates) {}
@Override
public void onEntering(List<DiscreteCoordinates> coordinates) {}
}
package ch.epfl.cs107.play.areagame.actor;
import java.util.Collections;
import java.util.List;
import ch.epfl.cs107.play.areagame.area.Area;
import ch.epfl.cs107.play.areagame.handler.AreaInteractionVisitor;
import ch.epfl.cs107.play.engine.actor.Sprite;
import ch.epfl.cs107.play.math.DiscreteCoordinates;
import ch.epfl.cs107.play.math.Orientation;
import ch.epfl.cs107.play.window.Canvas;
public class CellMouseIndicator extends AreaEntity {
private final Sprite overSprite;
public CellMouseIndicator(Area area) {
super(area, Orientation.UP, new DiscreteCoordinates(0, 0));
overSprite = new Sprite("cellOver", 1, 1, this);
overSprite.setDepth(1);
}
@Override
public List<DiscreteCoordinates> getCurrentCells() {
return Collections.singletonList(getCurrentMainCellCoordinates());
}
@Override
public boolean takeCellSpace() {
return false;
}
@Override
public boolean isCellInteractable() {
return false;
}
@Override
public boolean isViewInteractable() {
return false;
}
@Override
public void acceptInteraction(AreaInteractionVisitor v, boolean isCellInteraction) {}
@Override
public void update(float deltaTime) {
if(getOwnerArea().getRelativeMouseCoordinates() != getCurrentMainCellCoordinates()) {
List<DiscreteCoordinates> enteringCells = Collections.singletonList(getOwnerArea().getRelativeMouseCoordinates());
List<DiscreteCoordinates> leavingCells = getCurrentCells();
if(getOwnerArea().enterAreaCells(this, enteringCells) && getOwnerArea().leaveAreaCells(this, leavingCells))
setCurrentPosition(getOwnerArea().getRelativeMouseCoordinates().toVector());
}
super.update(deltaTime);
}
@Override
public void draw(Canvas canvas) {
overSprite.draw(canvas);
}
}
package ch.epfl.cs107.play.areagame.actor;
import ch.epfl.cs107.play.areagame.actor.AreaEntity;
import ch.epfl.cs107.play.areagame.area.Area;
import ch.epfl.cs107.play.math.DiscreteCoordinates;
import ch.epfl.cs107.play.math.Orientation;
public abstract class CollectableAreaEntity extends AreaEntity {
/// Flag on the collected status
private boolean isCollected;
/**
* Default CollectableAreaEntity constructor
* @param area (Area): Owner area. Not null
* @param position (Coordinate): Initial position of the entity. Not null
* @param orientation (Orientation): Initial orientation of the entity. Not null
*/
public CollectableAreaEntity(Area area, Orientation orientation, DiscreteCoordinates position) {
super(area, orientation, position);
isCollected = false;
}
/**
* Alternative CollectableAreaEntity constructor
* @param area (Area): Owner area. Not null
* @param position (Coordinate): Initial position of the entity. Not null
* @param orientation (Orientation): Initial orientation of the entity. Not null
* @param isCollected (boolean): initial collected status of this
*/
public CollectableAreaEntity(Area area, Orientation orientation, DiscreteCoordinates position, boolean isCollected) {
super(area, orientation, position);
this.isCollected = isCollected;
}
/** Collect the object (remove it form the area actor list and set flag to true) */
public void collect() {
if (!isCollected) {
isCollected = true;
}
}
public boolean isCollected(){
return isCollected;
}
}
package ch.epfl.cs107.play.areagame.actor;
import ch.epfl.cs107.play.areagame.actor.AreaEntity;
import ch.epfl.cs107.play.engine.DragHelper;
import ch.epfl.cs107.play.engine.actor.Draggable;
import ch.epfl.cs107.play.areagame.area.Area;
import ch.epfl.cs107.play.math.DiscreteCoordinates;
import ch.epfl.cs107.play.math.Orientation;
import ch.epfl.cs107.play.math.Vector;
import ch.epfl.cs107.play.window.Mouse;
public abstract class DraggableAreaEntity extends AreaEntity implements Draggable {
private final Mouse mouse;
private boolean isDragging;
private DiscreteCoordinates initialPosition;
private Vector relativeMousePosition;
private boolean wantsDropInteraction;
public DraggableAreaEntity(Area area, Orientation orientation, DiscreteCoordinates position) {
super(area, orientation, position);
mouse = area.getMouse();
}
@Override
public void update(float deltaTime) {
super.update(deltaTime);
if(wantsDropInteraction) { // the drop failed since we do not get any acknowledgement
resetDrag();
setCurrentPosition(initialPosition.toVector());
}
if(canDrag()) {
isDragging = true;
initialPosition = getCurrentMainCellCoordinates();
relativeMousePosition = getPosition().sub(getOwnerArea().getRelativeMousePosition());
DragHelper.setCurrentDraggedElement(this);
}
if(isDragging) {
if(mouse.getLeftButton().isReleased()) {
wantsDropInteraction = true;
}else {
setCurrentPosition(getOwnerArea().getRelativeMousePosition().add(relativeMousePosition));
}
}
}
private void resetDrag() {
isDragging = false;
wantsDropInteraction = false;
DragHelper.setCurrentDraggedElement(null);
}
public DiscreteCoordinates getInitialPosition() {
return initialPosition;
}
@Override
public boolean canDrag() {
return mouse.getLeftButton().isPressed() && isMouseOver();
}
@Override
public boolean isDragging() {
return isDragging;
}
@Override
public boolean wantsDropInteraction() {
return wantsDropInteraction;
}
@Override
public void acknowledgeDrop() {
resetDrag();
}
}
package ch.epfl.cs107.play.areagame.actor;
import ch.epfl.cs107.play.areagame.handler.AreaInteractionVisitor;
import ch.epfl.cs107.play.math.DiscreteCoordinates;
import java.util.List;
/**
* Represent Interactable object (i.e. Interactor can interact with it)
* @see Interactor
* This interface makes sense only in the "Area Context" with Actor contained into Area Cell
*/
public interface Interactable {
/**
* Get this Interactor's current occupying cells coordinates
* @return (List of DiscreteCoordinates). May be empty but not null
*/
List<DiscreteCoordinates> getCurrentCells();
/**
* Indicate if the current Interactable take the whole cell space or not
* i.e. only one Interactable which takeCellSpace can be in a cell
* (how many Interactable which don't takeCellSpace can also be in the same cell)
* @return (boolean)
*/
boolean takeCellSpace();
/**@return (boolean): true if this is able to have cell interactions*/
boolean isCellInteractable();
/**@return (boolean): true if this is able to have view interactions*/
boolean isViewInteractable();
/** Call directly the interaction on this if accepted
* @param v (AreaInteractionVisitor) : the visitor
* */
void acceptInteraction(AreaInteractionVisitor v, boolean isCellInteraction);
/**
* Called when this Interactable leaves a cell
* @param coordinates left cell coordinates
*/
void onLeaving(List<DiscreteCoordinates> coordinates);
/**
* Called when this Interactable enters a cell
* @param coordinates entered cell coordinates
*/
void onEntering(List<DiscreteCoordinates> coordinates);
/// Interactable Listener
interface Listener{
/**
* Indicate if the given Interactable entity can leave the cell at given coordinates
* @param entity (Interactable). Not null
* @param coordinates (List of DiscreteCoordinates). Not null
* @return (boolean): true if the entity can leave all the given cell
*/
boolean canLeave(Interactable entity, List<DiscreteCoordinates> coordinates);
/**
* Indicate if the given Interactable entity can enter the cell at given coordinates
* @param entity (Interactable). Not null
* @param coordinates (List of DiscreteCoordinates). Not null
* @return (boolean): true if the entity can enter all the given cell
*/
boolean canEnter(Interactable entity, List<DiscreteCoordinates> coordinates);
/**
* Do the given interactable entity leave the given cells
* @param entity (Interactable). Not null
* @param coordinates (List of DiscreteCoordinates). Not null
*/
void leave(Interactable entity, List<DiscreteCoordinates> coordinates);
/**
* Do the given interactable entity enter the given cells
* @param entity (Interactable). Not null
* @param coordinates (List of DiscreteCoordinates). Not null
*/
void enter(Interactable entity, List<DiscreteCoordinates> coordinates);
}
}
package ch.epfl.cs107.play.areagame.actor;
import ch.epfl.cs107.play.math.DiscreteCoordinates;
import java.util.List;
/**
* Represents Interactor object (i.e. it can interact with some Interactable)
* @see Interactable
* This interface makes sense only in the "Area Context" with Actor contained into Area Cell
*/
public interface Interactor {
/**
* Get this Interactor's current occupying cells coordinates
* @return (List of DiscreteCoordinates). May be empty but not null
*/
List<DiscreteCoordinates> getCurrentCells();
/**
* Get this Interactor's current field of view cells coordinates
* @return (List of DiscreteCoordinates). May be empty but not null
*/
List<DiscreteCoordinates> getFieldOfViewCells();
/**@return (boolean): true if this require cell interaction */
boolean wantsCellInteraction();
/**@return (boolean): true if this require view interaction */
boolean wantsViewInteraction();
/**
* Do this Interactor interact with the given Interactable
* The interaction is implemented on the interactor side !
* @param other (Interactable). Not null
* @param isCellInteraction True if this is a cell interaction
*/
void interactWith(Interactable other, boolean isCellInteraction);
/// Interactable Listener
interface Listener {
/**
* Do the interactor interact with Interactable (i.e. Interactable sharing the same cell)
* @param interactor (Interactor). Not null
*/
void cellInteractionOf(Interactor interactor);
/**
* Do the interactor interact with Interactable (i.e. Interactable in its field of view cells)
* @param interactor (Interactor). Not null
*/
void viewInteractionOf(Interactor interactor);
}
}
package ch.epfl.cs107.play.areagame.actor;
import ch.epfl.cs107.play.areagame.area.Area;
import ch.epfl.cs107.play.math.DiscreteCoordinates;
import ch.epfl.cs107.play.math.Orientation;
import ch.epfl.cs107.play.math.Vector;
import java.util.List;
/**
* MovableAreaEntity represent AreaEntity which can move on the grid
*/
public abstract class MovableAreaEntity extends AreaEntity {
/// Indicate if a displacement occurs right now
private boolean displacementOccurs;
/// Indicate how many frames the current move is supposed to take
private int framesForCurrentMove;
/// Indicate how many remaining frames the current move has
private int remainingFramesForCurrentMove;
/// Indicate if the entity entered the new cells and left the unused ones
private boolean cellsSwapped;
private List<DiscreteCoordinates> originCells;
private List<DiscreteCoordinates> targetCells;
private Vector targetPosition;
private Vector originPosition;
/**
* Default MovableAreaEntity constructor
*
* @param area (Area): Owner area. Not null
* @param position (Coordinate): Initial position of the entity. Not null
* @param orientation (Orientation): Initial orientation of the entity. Not null
*/
public MovableAreaEntity(Area area, Orientation orientation, DiscreteCoordinates position) {
super(area, orientation, position);
resetMotion();
}
/**
* Initialize or reset (if some) the current motion information
*/
protected void resetMotion() {
this.displacementOccurs = false;
this.framesForCurrentMove = 0;
this.remainingFramesForCurrentMove = 0;
this.cellsSwapped = false;
originCells = null;
targetCells = null;
}
/**
* Final move method
* If no displacement occurs or if the displacement just ends now,
* start movement of one Cell in the current Orientation direction
* Note the movement is possible only if this MovableAreaEntity can:
* - leave the cells this motion implies to leave
* - enter the cells this motion implies to enter
*
* @param frameForMove (int): the frame. This value will be cropped to 1 if smaller
* @return (boolean): indicate if the move is initiated
*/
protected final boolean move(int frameForMove) {
return move(frameForMove, 0);
}
/**
* Final move method
* If no displacement occurs or if the displacement just ends now,
* start movement of one Cell in the current Orientation direction
* Note the movement is possible only if this MovableAreaEntity can:
* - leave the cells this motion implies to leave
* - enter the cells this motion implies to enter
*
* @param frameForMove (int): the frame. This value will be cropped to 1 if smaller
* @param startingFrame (int): start the movement directly from this frame
* @return (boolean): indicate if the move is initiated
*/
protected final boolean move(int frameForMove, int startingFrame) {
if (!displacementOccurs || isTargetReached()) {
List<DiscreteCoordinates> originCells = getCurrentCells();
List<DiscreteCoordinates> targetCells = getNextCells(getPosition().add(getOrientation().toVector()));
List<DiscreteCoordinates> enteringCells = targetCells.stream().filter(coords -> !originCells.contains(coords)).toList();
List<DiscreteCoordinates> leavingCells = originCells.stream().filter(coords -> !targetCells.contains(coords)).toList();
if (getOwnerArea().canEnterAreaCells(this, enteringCells) && getOwnerArea().canLeaveAreaCells(this, leavingCells)) {
displacementOccurs = true;
this.originCells = originCells;
this.targetCells = targetCells;
this.cellsSwapped = false;
this.framesForCurrentMove = Math.max(1, frameForMove);
startingFrame = Math.min(startingFrame, frameForMove);
remainingFramesForCurrentMove = framesForCurrentMove - startingFrame;
originPosition = getPosition();
targetPosition = getPosition().add(getOrientation().toVector());
increasePositionOf(startingFrame);
return true;
}
}
return false;
}
private List<DiscreteCoordinates> getNextCells(Vector nextPosition) {
final Vector oldPosition = getPosition();
setCurrentPosition(nextPosition);
final List<DiscreteCoordinates> result = getCurrentCells();
setCurrentPosition(oldPosition);
return result;
}
/**
* Change the unit position to the one specified
*
* @param newPosition new unit's position
* @return true if the move was successful, false otherwise
*/
public boolean changePosition(DiscreteCoordinates newPosition) {
if (newPosition.equals(getCurrentMainCellCoordinates())) {
return true;
}
List<DiscreteCoordinates> currentCells = getCurrentCells();
List<DiscreteCoordinates> nextCells = getNextCells(newPosition.toVector());
final List<DiscreteCoordinates> enteringCells = nextCells.stream().filter(coords -> !currentCells.contains(coords)).toList();
final List<DiscreteCoordinates> leavingCells = currentCells.stream().filter(coords -> !nextCells.contains(coords)).toList();
if (getOwnerArea().canEnterAreaCells(this, enteringCells) && getOwnerArea().leaveAreaCells(this, leavingCells)) {
getOwnerArea().enterAreaCells(this, enteringCells);
setCurrentPosition(newPosition.toVector());
resetMotion();
return true;
}
System.out.println("Position change impossible. Destination is most likely occupied");
return false;
}
/**
* Final abortCurrentMove method
* If a displacement occurs and if the displacement is not end,
* abort the current move, returning to the previous cell
* Note the abort is possible only if this MovableAreaEntity can:
* - return to the cells it left
* - leave the cells it entered
*
* @return (boolean): indicate if the abort is initiated
*/
protected final boolean abortCurrentMove() {
if (!displacementOccurs) {
return false;
}
if (cellsSwapped) {
final List<DiscreteCoordinates> enteringCells = targetCells.stream().filter(coords -> !originCells.contains(coords)).toList();
final List<DiscreteCoordinates> leavingCells = originCells.stream().filter(coords -> !targetCells.contains(coords)).toList();
if (getOwnerArea().canEnterAreaCells(this, leavingCells) && getOwnerArea().canLeaveAreaCells(this, enteringCells)) {
cellsSwapped = false;
} else {
return false;
}
} else {
cellsSwapped = true;
}
remainingFramesForCurrentMove = framesForCurrentMove - remainingFramesForCurrentMove;
Vector tempPos = originPosition;
originPosition = targetPosition;
targetPosition = tempPos;
List<DiscreteCoordinates> tempCells = originCells;
originCells = targetCells;
targetCells = tempCells;
return true;
}
/**
* Indicate if a displacement is occurring
*
* @return (boolean)
*/
protected boolean isDisplacementOccurs() {
return displacementOccurs;
}
/**
* @return (boolean): true when the target cell is just reaching now
*/
protected boolean isTargetReached() {
return remainingFramesForCurrentMove == 0;
}
/**
* Increase the position of a certain amount of frame
*
* @param frame
*/
private void increasePositionOf(int frame) {
final DiscreteCoordinates before = getCurrentMainCellCoordinates();
setCurrentPosition(getPosition().add(getOrientation().toVector().mul(frame / (float) framesForCurrentMove)));
final DiscreteCoordinates after = getCurrentMainCellCoordinates();
if (!before.equals(after)) {
final List<DiscreteCoordinates> enteringCells = targetCells.stream().filter(coords -> !originCells.contains(coords)).toList();
final List<DiscreteCoordinates> leavingCells = originCells.stream().filter(coords -> !targetCells.contains(coords)).toList();
if (getOwnerArea().canEnterAreaCells(this, enteringCells) && getOwnerArea().leaveAreaCells(this, leavingCells)) {
getOwnerArea().enterAreaCells(this, enteringCells);
cellsSwapped = true;
} else {
orientate(getOrientation().opposite());
}
}
}
/// MovableAreaEntity extends AreaEntity
@Override
protected boolean orientate(Orientation orientation) {
// Allow reorientation only if no displacement is occurring or if abort current move (opposite orientation)
if (getOrientation().opposite().equals(orientation)) {
if (abortCurrentMove())
return super.orientate(orientation);
}
return !displacementOccurs && super.orientate(orientation);
}
/// MovableAreaEntity implements Actor
@Override
public void update(float deltaTime) {
if (displacementOccurs) {
if (!isTargetReached()) {
increasePositionOf(1);
} else {
setCurrentPosition(targetPosition);
resetMotion();
}
}
remainingFramesForCurrentMove = Math.max(remainingFramesForCurrentMove - 1, 0);
}
/// Implements Positionable
@Override
public Vector getVelocity() {
return getOrientation().toVector().mul(framesForCurrentMove);
}
}
package ch.epfl.cs107.play.areagame.area;
import ch.epfl.cs107.play.areagame.actor.Interactable;
import ch.epfl.cs107.play.areagame.actor.Interactor;
import ch.epfl.cs107.play.engine.DragHelper;
import ch.epfl.cs107.play.engine.PauseMenu;
import ch.epfl.cs107.play.engine.Playable;
import ch.epfl.cs107.play.engine.actor.Actor;
import ch.epfl.cs107.play.engine.actor.Draggable;
import ch.epfl.cs107.play.engine.actor.Graphics;
import ch.epfl.cs107.play.io.FileSystem;
import ch.epfl.cs107.play.math.DiscreteCoordinates;
import ch.epfl.cs107.play.math.Transform;
import ch.epfl.cs107.play.math.Vector;
import ch.epfl.cs107.play.window.Canvas;
import ch.epfl.cs107.play.window.Keyboard;
import ch.epfl.cs107.play.window.Mouse;
import ch.epfl.cs107.play.window.Window;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Area is a "Part" of the AreaGame. An Area is made of a Behavior, and a List of Actors
*/
public abstract class Area implements Playable, Graphics, PauseMenu.Pausable {
// Context objects
private Window window;
private FileSystem fileSystem;
// Camera Parameter
private Actor viewCandidate;
private Vector viewCenter;
/// List of Actors inside the area
private List<Actor> actors;
/// List of Actors we want to register/unregistered from the area for next update iteration
private List<Actor> registeredActors;
private List<Actor> unregisteredActors;
/// Sublist of actor (interactors) inside the area
private List<Interactor> interactors;
private Map<Interactable, List<DiscreteCoordinates>> interactablesToEnter;
private Map<Interactable, List<DiscreteCoordinates>> interactablesToLeave;
/// The behavior Map
private AreaBehavior areaBehavior;
/// pause mechanics and menu to display. May be null
/// - start indicate if area already begins, paused indicate if we display the pause menu
private boolean started, paused;
private AreaPauseMenu menu;
/**
* @return (float): camera scale factor, assume it is the same in x and y direction
*/
public abstract float getCameraScaleFactor();
/**
* Setter for the Behavior of this Area
* Please call this method in the begin method of every subclass
*
* @param ab (AreaBehavior), not null
*/
protected final void setBehavior(AreaBehavior ab) {
this.areaBehavior = ab;
}
/**
* Setter for the view Candidate
*
* @param a (Actor), not null
*/
public final void setViewCandidate(Actor a) {
this.viewCandidate = a;
}
/**
* Add an actor to the actors list
* and to the behavior area cell if the actor is an Interactable
* and to the interactor list if the actor is an Interactor
*
* @param a (Actor)(Interactor?)(Interactable?): the actor to add, not null
* @param safeMode (Boolean): if True, the method ends
*/
private void addActor(Actor a, boolean safeMode) {
boolean errorHappen = false;
if (a instanceof Interactor)
errorHappen = !interactors.add((Interactor) a);
if (a instanceof Interactable)
errorHappen = errorHappen || !enterAreaCells(((Interactable) a), ((Interactable) a).getCurrentCells());
errorHappen = errorHappen || !actors.add(a);
if (errorHappen && !safeMode) {
System.out.println("Actor " + a + " cannot be completely added, so remove it from where it was");
// Call it in safe mode to avoid recursive calls
removeActor(a, true);
}
}
/**
* Remove an actor form the actor list
* and from the behavior area cell if the actor is an Interactable
* and from the interactor list if the actor is an Interactor
*
* @param a (Actor): the actor to remove, not null
* @param safeMode (Boolean): if True, the method ends
*/
private void removeActor(Actor a, boolean safeMode) {
boolean errorHappen = false;
if (a instanceof Interactor)
errorHappen = !interactors.remove((Interactor) a);
if (a instanceof Interactable)
errorHappen = errorHappen || !leaveAreaCells(((Interactable) a), ((Interactable) a).getCurrentCells());
errorHappen = errorHappen || !actors.remove(a);
if (errorHappen && !safeMode) {
System.out.println("Actor " + a + " cannot be completely removed, so add it from where it was");
// Call it in safe mode to avoid recursive calls
addActor(a, true);
}
}
/**
* Register an actor : will be added at next update
*
* @param a (Actor): the actor to register, not null
* @return (boolean): true if the actor is correctly registered
*/
public final boolean registerActor(Actor a) {
// if actor can be registered: It is this Area decision, implement a strategy if needed
return registeredActors.add(a);
}
/**
* Unregister an actor : will be removed at next update
*
* @param a (Actor): the actor to unregister, not null
* @return (boolean): true if the actor is correctly unregistered
*/
public final boolean unregisterActor(Actor a) {
// if actor can be unregistered: It is this Area decision, implement a strategy if needed
return unregisteredActors.add(a);
}
/**
* Indicate if the given actor exists into the actor list
*
* @param a (Actor): the given actor, may be null
* @return (boolean): true if the given actor exists into actor list
*/
public boolean exists(Actor a) {
return actors.contains(a);
}
/**
* Getter for the area width
*
* @return (int) : the width in number of cols
*/
public int getWidth() {
return areaBehavior.getWidth();
}
/**
* Getter for the area height
*
* @return (int) : the height in number of rows
*/
public int getHeight() {
return areaBehavior.getHeight();
}
/**
* @return the Window Keyboard for inputs
*/
public final Keyboard getKeyboard() {
return window.getKeyboard();
}
/**
* @return the Window Mouse for inputs
*/
public final Mouse getMouse() {
return window.getMouse();
}
/**
* @return the mouse position relatively to the area and the cells
*/
public Vector getRelativeMousePosition() {
return getMouse().getPosition();
/*.max(new Vector(0,0))
.min(new Vector(getWidth(),getHeight()));*/
}
/**
* @return the mouse coordinates relatively to the area and the cells
*/
public DiscreteCoordinates getRelativeMouseCoordinates() {
Vector mousePosition = getRelativeMousePosition();
DiscreteCoordinates mouseCoordinate = new DiscreteCoordinates((int) Math.floor(mousePosition.x), (int) Math.floor(mousePosition.y));
return mouseCoordinate;
}
/**
* @return (boolean): true if the method begin already called once. You can use resume() instead
*/
public final boolean isStarted() {
return started;
}
/**
* If possible make the given interactable entity leave the given area cells
*
* @param entity (Interactable), not null
* @param coordinates (List of DiscreteCoordinates), may be empty but not null
* @return (boolean): True if possible to leave
*/
public final boolean leaveAreaCells(Interactable entity, List<DiscreteCoordinates> coordinates) {
// Until now, the entity is put in a map waiting the update end to avoid concurrent exception during interaction
if (areaBehavior.canLeave(entity, coordinates)) {
List<DiscreteCoordinates> orDefault = interactablesToLeave.getOrDefault(entity, new ArrayList<>());
orDefault.addAll(coordinates);
interactablesToLeave.put(entity, orDefault);
return true;
}
return false;
}
/**
* If possible make the given interactable entity enter the given area cells
*
* @param entity (Interactable), not null
* @param coordinates (List of DiscreteCoordinates), may be empty but not null
* @return (boolean): True if possible to enter
*/
public final boolean enterAreaCells(Interactable entity, List<DiscreteCoordinates> coordinates) {
// Until now, the entity is put in a map waiting the update end to avoid concurrent exception during interaction
if (areaBehavior.canEnter(entity, coordinates)) {
interactablesToEnter.put(entity, coordinates);
return true;
}
return false;
}
/**
* Inform if the entity can enter the area cells
*
* @param entity (Interactable), not null
* @param coordinates (List of DiscreteCoordinates), may be empty but not null
* @return (boolean): True if possible to enter
*/
public final boolean canEnterAreaCells(Interactable entity, List<DiscreteCoordinates> coordinates) {
return areaBehavior.canEnter(entity, coordinates);
}
/**
* Inform if the entity can leave the area cells
*
* @param entity (Interactable), not null
* @param coordinates (List of DiscreteCoordinates), may be empty but not null
* @return (boolean): True if possible to leave
*/
public final boolean canLeaveAreaCells(Interactable entity, List<DiscreteCoordinates> coordinates) {
return areaBehavior.canLeave(entity, coordinates);
}
/// Area implements Playable
@Override
public boolean begin(Window window, FileSystem fileSystem) {
this.window = window;
this.fileSystem = fileSystem;
actors = new LinkedList<>();
interactors = new LinkedList<>();
registeredActors = new LinkedList<>();
unregisteredActors = new LinkedList<>();
interactablesToEnter = new HashMap<>();
interactablesToLeave = new HashMap<>();
viewCenter = Vector.ZERO;
paused = false;
started = true;
return true;
}
/**
* Resume method: Can be overridden
*
* @param window (Window): display context, not null
* @param fileSystem (FileSystem): given file system, not null
* @return (boolean) : if the resume succeed, true by default
*/
public boolean resume(Window window, FileSystem fileSystem) {
return true;
}
@Override
public void update(float deltaTime) {
purgeRegistration();
// Decide if we update the contextual menu or this content
if (paused && menu != null) {
menu.update(deltaTime);
} else {
// Render actors
for (Actor actor : actors) {
actor.update(deltaTime);
}
Draggable currentDraggedElement = DragHelper.getCurrentDraggedElement();
if (currentDraggedElement != null && currentDraggedElement.wantsDropInteraction()) {
areaBehavior.dropInteractionOf(currentDraggedElement, getRelativeMouseCoordinates());
}
// Realize interaction between interactors and their cells contents
for (Interactor interactor : interactors) {
if (interactor.wantsCellInteraction()) {
areaBehavior.cellInteractionOf(interactor);
}
if (interactor.wantsViewInteraction()) {
areaBehavior.viewInteractionOf(interactor);
}
}
// Update camera location
updateCamera();
}
}
@Override
public void draw(Canvas canvas) {
if (paused && menu != null) {
return;
}
for (Actor actor : actors) {
actor.bip(window);
actor.draw(window);
}
}
public final void purgeRegistration() {
// - unregister actors
for (Actor actor : unregisteredActors) {
removeActor(actor, false);
}
// - before clearing the unregistered actors, remove their actions from
// interactablesToEnter for a complete and correct unregistration
for (Actor actor : unregisteredActors) {
if (actor instanceof Interactable interactable) {
interactablesToEnter.remove(interactable);
}
}
unregisteredActors.clear();
// - leave old cells
for (Map.Entry<Interactable, List<DiscreteCoordinates>> entry : interactablesToLeave.entrySet()) {
areaBehavior.leave(entry.getKey(), entry.getValue());
entry.getKey().onLeaving(entry.getValue());
}
interactablesToLeave.clear();
// - Register actors
for (Actor actor : registeredActors) {
addActor(actor, false);
}
registeredActors.clear();
// - enter new cells
for (Map.Entry<Interactable, List<DiscreteCoordinates>> entry : interactablesToEnter.entrySet()) {
areaBehavior.enter(entry.getKey(), entry.getValue());
entry.getKey().onEntering(entry.getValue());
}
interactablesToEnter.clear();
}
private void updateCamera() {
if (!isViewCentered()) {
// Update expected viewport center
if (viewCandidate != null) {
viewCenter = viewCandidate.getPosition();
} else { // Set default view to center
viewCenter = new Vector(getWidth() / (float) 2, getHeight() / (float) 2);
}
// Compute new viewport
Transform viewTransform = Transform.I.scaled(getCameraScaleFactor()).translated(viewCenter);
window.setRelativeTransform(viewTransform);
return;
}
float frameSize = Math.min(this.getHeight(), this.getWidth());
float midX = getWidth() / (float) 2;
float midY = getHeight() / (float) 2;
viewCenter = new Vector(midX, midY);
if (viewCandidate != null) {
Vector position = viewCandidate.getPosition();
if (this.getHeight() > 1.2 * this.getWidth()) {
viewCenter = new Vector(midX, position.getY());
} else if (1.2 * this.getHeight() < this.getWidth()) {
viewCenter = new Vector(position.getX(), midY);
}
}
// Compute new viewport
Transform viewTransform = Transform.I.scaled(Math.max(getCameraScaleFactor(), frameSize)).translated(viewCenter);
//Transform viewTransform = Transform.I.scaled(frameSize).translated(viewCenter);
// adapt to background image
//.translated((float) this.getWidth() /2, (float) this.getHeight() /2);
window.setRelativeTransform(viewTransform);
}
/**
* Suspend method: Can be overridden, called before resume other
*/
public void suspend() {
// Do nothing by default
}
@Override
public void end() {
// save the AreaState somewhere if needed
}
/// Area Implements PauseMenu.Pausable
/**
* Can be called by any possessor of this Area.
* Caller indicate it requests a pause with given menu displayed.
* Notice: this method chooses if the request ends up or not
*
* @param menu (AreaPauseMenu): The context menu to display. It (or any of its components) will
* be responsible of the ResumeRequest, not null
*/
public final void requestAreaPause(AreaPauseMenu menu) {
if (menu != null) {
this.menu = menu;
// Important to begin the menu each time : isResumeRequested must be set to false
this.menu.begin(window, fileSystem);
this.menu.setOwner(this);
}
requestPause();
}
@Override
public final void requestPause() {
this.paused = true;
}
/**
* Can be called by anny possessor of this Area
* Caller indicates it requests a resume of the pause state to the game
* Notice: this method chooses if the request ends up or not
*/
@Override
public final void requestResume() {
this.paused = false;
}
@Override
public final boolean isPaused() {
return paused;
}
public boolean isViewCentered() { return false; }
}