Compare commits

...

12 Commits

Author SHA1 Message Date
Jaime Freire
fbe95c4c04 Minor fixes 2024-01-03 20:22:02 +01:00
Jaime Freire
a2c2e9faf6 Tests, fixes; working version 2024-01-03 18:41:25 +01:00
Jaime Freire
c8a7f22a45 Cleanups and fixed test 2024-01-03 09:58:58 +01:00
Jaime Freire
4abcfc2178 Deleting binaries.. 2024-01-03 09:37:33 +01:00
Jaime Freire
70f9ad4d99 Git Ignore 2024-01-03 09:28:09 +01:00
Jaime Freire
d29d2e53bd Finishing protocol fixes 2024-01-03 00:36:15 +01:00
Jaime Freire
965636544e Refactoring splitting Room and Session 2024-01-02 23:03:46 +01:00
Jaime Freire
12b7224510 Milestone 2024-01-02 16:44:56 +01:00
Jaime Freire
1d6505a2ca Git Ignore 2023-12-24 10:26:29 +01:00
Jaime Freire
6cb6de28fc Basic behavior ready 2023-12-24 10:25:45 +01:00
Jaime Freire
76e1781864 Some drafts 2023-12-23 12:17:47 +01:00
Jaime Freire
3b223dfea2 Chat 2023-12-22 09:03:25 +01:00
23 changed files with 1374 additions and 0 deletions

183
.gitignore vendored Normal file
View File

@ -0,0 +1,183 @@
/target/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
# Created by https://www.gitignore.io/api/git,java,maven,eclipse,windows
### Eclipse ###
.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
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Annotation Processing
.sts4-cache/
### Git ###
# Created by git for backups. To disable backups in Git:
# $ git config --global mergetool.keepBackup false
*.orig
# Created by git when using merge tools for conflicts
*.BACKUP.*
*.BASE.*
*.LOCAL.*
*.REMOTE.*
*_BACKUP_*.txt
*_BASE_*.txt
*_LOCAL_*.txt
*_REMOTE_*.txt
### Java ###
# 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*
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
### Windows ###
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
/build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Some additional ignores (sort later)
*.DS_Store
*.sw?
.#*
*#
*~
bin
build
target
*.sublime-*
/scratch
.gradle
README.html
.exercism
/target/
/.idea/

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

15
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile default="true" name="Default" enabled="true" />
<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="social_altruism" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel target="19" />
</component>
</project>

6
.idea/google-java-format.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoogleJavaFormatSettings">
<option name="enabled" value="true" />
</component>
</project>

17
.idea/gradle.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="corretto-19" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

35
.idea/jarRepositories.xml generated Normal file
View File

@ -0,0 +1,35 @@
<?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="MavenRepo" />
<option name="name" value="MavenRepo" />
<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>
<remote-repository>
<option name="id" value="MavenLocal" />
<option name="name" value="MavenLocal" />
<option name="url" value="file:$MAVEN_REPOSITORY$/" />
</remote-repository>
<remote-repository>
<option name="id" value="akka-repository" />
<option name="name" value="Akka library repository" />
<option name="url" value="https://repo.akka.io/maven" />
</remote-repository>
</component>
</project>

6
.idea/jpa-buddy.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JpaBuddyIdeaProjectConfig">
<option name="renamerInitialized" value="true" />
</component>
</project>

16
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,16 @@
<?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>
<option name="workspaceImportForciblyTurnedOn" value="true" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="corretto-19" project-jdk-type="JavaSDK" />
<component name="ProjectType">
<option name="id" value="jpab" />
</component>
</project>

124
.idea/uiDesigner.xml generated Normal file
View File

@ -0,0 +1,124 @@
<?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>

7
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

23
README.txt Normal file
View File

@ -0,0 +1,23 @@
Juego del bien público
----------------------
- Varios jugadores.
- Reciben N monedas al comienzo.
- Hay un bote central.
- Cada turno se elige con cuántas monedas uno colabora al bote.
- Entonces, el contenido del bote se multiplica por dos y se divide entre todos los jugadores.
- Pasadas N rondas, el jugador se queda con lo que ganó + monedas iniciales.
- Tipos de jugadores:
- Pícaro: no colabora nunca.
- Santo: colabora siempre.
- Justiciero: colabora si y solo si todos los jugadores han colaborado en el turno anterior.
Comprobar distribución de ganancia según variación en tipo de jugadores; Pícaro, santo o justiciero.
10 jugadores, 100 turnos, 100 monedas iniciales.
Comprobar cómo de dispuesta está la gente a contribuir a un bien, que será compartido por todos por igual,
hayan contribuido o no.

122
pom.xml Normal file
View File

@ -0,0 +1,122 @@
<!-- #build-sample -->
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>hello-akka-java</groupId>
<artifactId>SocialAltruism</artifactId>
<version>1.0</version>
<properties>
<akka.version>2.9.0</akka.version>
</properties>
<repositories>
<repository>
<id>akka-repository</id>
<name>Akka library repository</name>
<url>https://repo.akka.io/maven</url>
</repository>
</repositories>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-reload4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor-typed_2.13</artifactId>
<version>${akka.version}</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor-testkit-typed_2.13</artifactId>
<version>${akka.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility-proxy</artifactId>
<version>3.1.6</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hamcrest/hamcrest -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>19</source>
<target>19</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<executable>java</executable>
<arguments>
<argument>-classpath</argument>
<classpath />
<argument>dev.freireservices.social_altruism.chat.PotQuickStart</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,84 @@
package dev.freireservices.social_altruism.chat;
import akka.actor.typed.ActorRef;
import akka.actor.typed.ActorSystem;
import akka.actor.typed.Behavior;
import akka.actor.typed.Terminated;
import akka.actor.typed.javadsl.Behaviors;
import dev.freireservices.social_altruism.chat.participant.Participant;
import dev.freireservices.social_altruism.chat.participant.ParticipantProtocol;
import dev.freireservices.social_altruism.chat.potroom.PotRoom;
import dev.freireservices.social_altruism.chat.potroom.PotRoomProtocol;
import java.util.ArrayList;
import java.util.List;
import static dev.freireservices.social_altruism.chat.participant.ParticipantType.*;
public class PotQuickStart {
public static final int MONEDAS_INIT = 100;
public static void main(String[] args) {
// #actor-system
ActorSystem.create(Main.create(), "PotRoom");
}
public static class Main {
static List<ActorRef<ParticipantProtocol.ParticipantMessage>> sessions = new ArrayList<>();
static final int numberOfParticipants = 3;
static final int numberOfTurns = 100;
public static Behavior<Void> create() {
return Behaviors.setup(
context -> {
ActorRef<PotRoomProtocol.PotRoomMessage> chatRoom =
context.spawn(
PotRoom.create(numberOfParticipants, numberOfTurns), "potRoom");
// Agregamos jugadores
final var picaroZero = Participant.create(MONEDAS_INIT, PICARO);
final var santoZero = Participant.create(MONEDAS_INIT, SANTO);
final var justiZero = Participant.create(MONEDAS_INIT, JUSTICIERO);
sessions.add(context.spawn(picaroZero, "participante-picaro-0"));
sessions.add(context.spawn(santoZero, "participante-santo-0"));
sessions.add(context.spawn(justiZero, "participante-justiciero-0"));
// Entrar en pot
for (ActorRef<ParticipantProtocol.ParticipantMessage> session : sessions) {
chatRoom.tell(new PotRoomProtocol.EnterPot(session));
}
/* for (int i = 0; i < numberOfTurns; i++) {
// Participar en cada turno cantidad diferente
for (ActorRef<ParticipantProtocol.ParticipantMessage> session : participants) {
// Pícaro
if (session.path().name().contains("participante-0")) {
chatRoom.tell(new PotRoomProtocol.PlayTurn(session, 0));
// Justiciero
} else if (session.path().name().contains("participante-1")) {
// ActorRef<Commands.RoomCommand> chatRoom, double pot,
// ActorRef<Events.SessionEvent> replyTo)
chatRoom.tell(new PotRoomProtocol.PlayTurn(session, 1));
// Santo
} else if (session.path().name().contains("participante-2")) {
chatRoom.tell(new PotRoomProtocol.PlayTurn(session, 2));
}
}
}*/
return Behaviors.receive(Void.class)
.onSignal(Terminated.class, sig -> Behaviors.stopped())
.build();
});
}
}
// #actor-system
}

View File

@ -0,0 +1,219 @@
package dev.freireservices.social_altruism.chat.participant;
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;
import dev.freireservices.social_altruism.chat.participant.ParticipantProtocol.*;
import dev.freireservices.social_altruism.chat.potroom.PotRoomProtocol.PotRoomMessage;
import dev.freireservices.social_altruism.chat.potroom.SessionProtocol;
import dev.freireservices.social_altruism.chat.potroom.SessionProtocol.SessionMessage;
import lombok.Getter;
import lombok.Setter;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.List;
import static dev.freireservices.social_altruism.chat.participant.ParticipantType.JUSTICIERO;
import static dev.freireservices.social_altruism.chat.participant.ParticipantType.SANTO;
@Setter
@Getter
public class Participant {
private final ActorContext<ParticipantMessage> context;
private ActorRef<PotRoomMessage> chatRoom;
private ActorRef<SessionMessage> session;
private boolean collaborateSwitch;
private int currentTurn;
private double participantCoins;
private final double initialCoins;
private List<ActorRef<ParticipantMessage>> participants;
private int totalTurns;
private final ParticipantType participantType;
private Participant(
ActorContext<ParticipantMessage> context,
double participantCoins,
ParticipantType participantType) {
this.context = context;
this.participantCoins = participantCoins;
this.initialCoins = participantCoins;
this.participantType = participantType;
this.collaborateSwitch = participantType == JUSTICIERO || participantType == SANTO;
}
public static Behavior<ParticipantMessage> create(
int initialCoins, ParticipantType participantType) {
return Behaviors.setup(ctx -> new Participant(ctx, initialCoins, participantType).behavior());
}
public double decrementCoins(double coins) {
this.participantCoins -= coins;
return coins;
}
public void incrementCoins(double coins) {
this.participantCoins += coins;
}
private Behavior<ParticipantMessage> behavior() {
return uninitialized();
}
private Behavior<ParticipantMessage> uninitialized() {
return Behaviors.receive(ParticipantMessage.class)
.onMessage(SessionDenied.class, this::onSessionDenied)
.onMessage(SessionGranted.class, x ->
{
onSessionGranted(x);
return readyToStart();
})
.build();
}
private Behavior<ParticipantMessage> readyToStart() {
return Behaviors.receive(ParticipantMessage.class)
.onMessage(SessionStarted.class,
x -> {
onSessionStarted(x);
return started();
}
)
.build();
}
private Behavior<ParticipantMessage> started() {
return Behaviors.receive(ParticipantMessage.class)
.onMessage(PotReturned.class, this::onPotReturned)
.onMessage(SessionEnded.class, this::onSessionEnded)
.build();
}
private Behavior<ParticipantMessage> onSessionEnded(SessionEnded sessionEnded) {
context.getLog().info("Session ended for user: Stats: {}. Earned {} coins, profit {} %"
, context.getSelf().path().name()
, String.format("%.3f%n", getParticipantCoins() - getInitialCoins())
, calculateProfit()
);
return Behaviors.stopped();
}
private Behavior<ParticipantMessage> onSessionDenied(
SessionDenied message) {
context.getLog().info("cannot start chat room session: {}", message.reason());
return Behaviors.stopped();
}
private void onSessionGranted(
SessionGranted message) {
context.getLog().info("Session granted message received for {} ", context.getSelf().path().name());
setChatRoom(message.chatRoom());
setSession(message.session());
}
private void onSessionStarted(
SessionStarted startSession) {
context.getLog().info("Session started for {} with {} participants", context.getSelf().path().name(), startSession.participants().size());
resetCurrentTurn();
setParticipants(startSession.participants());
setTotalTurns(startSession.totalTurns());
playTurnWithSmallDelay(startSession.replyTo());
}
private void playTurnWithSmallDelay(ActorRef<SessionMessage> replyTo) {
if (getParticipantCoins() > 0 && getCurrentTurn() < getTotalTurns()) {
context.scheduleOnce(Duration.ofMillis(500),
replyTo,
new SessionProtocol.PlayTurn(
replyTo,
context.getSelf(),
participants,
getCurrentTurn(),
getParticipationForCurrentTurn())
);
}
}
private double getParticipationForCurrentTurn() {
var currentTurnCoins = getRandomNumberBetween(0, Math.floor(getParticipantCoins()));
return isCollaborateSwitch() ? decrementCoins(currentTurnCoins) : 0;
}
public static double getRandomNumberBetween(double min, double max) {
SecureRandom secureRandom = new SecureRandom();
return Math.round(secureRandom.nextDouble(max - min) + min);
}
private Behavior<ParticipantMessage> onPotReturned(
PotReturned potReturned) {
context.getLog().info("Pot returned: {} for participant {}", String.format("%.2f", potReturned.returnedAmount()), potReturned.participant().path().name());
incrementCoins(potReturned.returnedAmount());
incrementCurrentTurn();
context
.getLog()
.info(
"Player {} has now {} coins; started with {} for a partial profit of: {} %",
potReturned.participant().path().name(),
String.format("%.3f", getParticipantCoins()),
getInitialCoins(),
calculateProfit());
// Still game?
if (getParticipantCoins() > 1) {
playTurnWithSmallDelay(potReturned.session());
} else {
context
.getLog()
.info(
"Player {} has now {} coins; started with {} for a total profit of: {} %",
potReturned.participant().path().name(),
getParticipantCoins(),
getInitialCoins(),
calculateProfit());
context.getLog().info("END GAME");
context.getLog().info("---------");
context.getLog().info("END GAME");
context.stop(context.getSelf());
Behaviors.stopped();
}
adjustBehaviour(potReturned);
return Behaviors.same();
}
private void adjustBehaviour(PotReturned potReturned) {
switch (participantType) {
case SANTO:
setCollaborateSwitch(true);
break;
case PICARO:
setCollaborateSwitch(false);
break;
case JUSTICIERO:
// Tweak minimum amount to collaborate; average contribution must be at least the same.
setCollaborateSwitch((potReturned.returnedAmount() / participants.size() >= getParticipationForCurrentTurn()));
break;
}
}
public void resetCurrentTurn() {
setCurrentTurn(0);
}
public void incrementCurrentTurn() {
setCurrentTurn(++this.currentTurn);
}
private double calculateProfit() {
return Math.round((getParticipantCoins() * 100) / getInitialCoins() - 100);
}
}

View File

@ -0,0 +1,45 @@
package dev.freireservices.social_altruism.chat.participant;
import akka.actor.typed.ActorRef;
import dev.freireservices.social_altruism.chat.potroom.PotRoomProtocol.PotRoomMessage;
import dev.freireservices.social_altruism.chat.potroom.SessionProtocol.SessionMessage;
import java.util.List;
public class ParticipantProtocol {
public interface ParticipantMessage {
}
enum Timeout implements ParticipantMessage {
INSTANCE
}
public record SessionGranted(
ActorRef<PotRoomMessage> chatRoom,
ActorRef<SessionMessage> session
)
implements ParticipantMessage {
}
public record SessionStarted(
ActorRef<PotRoomMessage> chatRoom,
ActorRef<SessionMessage> replyTo,
List<ActorRef<ParticipantMessage>> participants,
int totalTurns)
implements ParticipantMessage {
}
public record SessionDenied(String reason) implements ParticipantMessage {
}
public record SessionEnded() implements ParticipantMessage {
}
public record PotReturned(
ActorRef<SessionMessage> session,
ActorRef<ParticipantMessage> participant,
double returnedAmount)
implements ParticipantMessage {
}
}

View File

@ -0,0 +1,8 @@
package dev.freireservices.social_altruism.chat.participant;
public enum ParticipantType {
SANTO,
JUSTICIERO,
PICARO,
}

View File

@ -0,0 +1,95 @@
package dev.freireservices.social_altruism.chat.potroom;
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;
import dev.freireservices.social_altruism.chat.potroom.PotRoomProtocol.EnterPot;
import dev.freireservices.social_altruism.chat.potroom.PotRoomProtocol.PotRoomMessage;
import dev.freireservices.social_altruism.chat.potroom.SessionProtocol.SessionMessage;
import lombok.Getter;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import static dev.freireservices.social_altruism.chat.participant.ParticipantProtocol.*;
import static java.nio.charset.StandardCharsets.UTF_8;
@Getter
public class PotRoom {
private final ActorContext<PotRoomMessage> context;
private final List<ActorRef<ParticipantMessage>> participants;
private final int totalTurns;
private final int numberOfParticipants;
private PotRoom(
ActorContext<PotRoomMessage> context,
int numberOfParticipants,
int totalTurns) {
this.context = context;
this.participants = new ArrayList<>();
this.numberOfParticipants = numberOfParticipants;
this.totalTurns = totalTurns;
}
private Behavior<PotRoomMessage> onGetPotSession(
ActorRef<PotRoomMessage> chatRoom, EnterPot enterPot) {
validate(enterPot);
context.getLog().info("Participant joined {} pot", enterPot.replyTo().path().name());
ActorRef<ParticipantMessage> participant = enterPot.replyTo();
participants.add(participant);
if (getNumberOfParticipants() == participants.size()) {
context.getLog().info("All participants joined; pot is ready to start.");
ActorRef<SessionMessage> session =
context.spawn(
Session.create(participants, totalTurns),
URLEncoder.encode(enterPot.replyTo().path().name(), UTF_8));
// Communicate session start and share pot info with all participants
//Fishy, probably could spare one..
participants.forEach(p -> p.tell(new SessionGranted(chatRoom, session.narrow())));
participants.forEach(p -> p.tell(new SessionStarted(chatRoom, session, participants, totalTurns)));
return createPotBehaviour(chatRoom);
} else {
// Waiting for more participants
context.getLog().info("Waiting for {} more participant(s).", numberOfParticipants - participants.size());
return Behaviors.same();
}
}
private void validate(EnterPot enterPot) {
// Add check session started
if (participants.stream()
.anyMatch(
s ->
s.path()
.name()
.equals(URLEncoder.encode(enterPot.replyTo().path().name(), UTF_8)))) {
enterPot.replyTo().tell(new SessionDenied("Can only enter a pot once"));
}
}
private Behavior<PotRoomMessage> createPotBehaviour(ActorRef<PotRoomMessage> chatRoom) {
return Behaviors.receive(PotRoomMessage.class)
.onMessage(EnterPot.class, x -> onGetPotSession(chatRoom, x))
.build();
}
public static Behavior<PotRoomMessage> create(int numberOfParticipants, int totalTurns) {
return Behaviors.setup(
ctx -> new PotRoom(ctx, numberOfParticipants, totalTurns)
.createPotBehaviour(ctx.getSelf()));
}
}

View File

@ -0,0 +1,12 @@
package dev.freireservices.social_altruism.chat.potroom;
import akka.actor.typed.ActorRef;
import static dev.freireservices.social_altruism.chat.participant.ParticipantProtocol.ParticipantMessage;
public class PotRoomProtocol {
public interface PotRoomMessage {}
public record EnterPot(ActorRef<ParticipantMessage> replyTo)
implements PotRoomMessage {}
}

View File

@ -0,0 +1,129 @@
package dev.freireservices.social_altruism.chat.potroom;
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;
import dev.freireservices.social_altruism.chat.participant.ParticipantProtocol.ParticipantMessage;
import dev.freireservices.social_altruism.chat.participant.ParticipantProtocol.PotReturned;
import dev.freireservices.social_altruism.chat.participant.ParticipantProtocol.SessionEnded;
import dev.freireservices.social_altruism.chat.participant.ParticipantProtocol.SessionStarted;
import dev.freireservices.social_altruism.chat.potroom.PotRoomProtocol.PotRoomMessage;
import dev.freireservices.social_altruism.chat.potroom.SessionProtocol.*;
import lombok.Getter;
import java.time.Duration;
import java.util.List;
@Getter
public class Session {
private final ActorContext<SessionMessage> context;
private int currentTurn = 0;
private final int totalTurns;
private final List<ActorRef<ParticipantMessage>> participants;
private double currentPot = 0.0;
private int numberOfParticipantsInCurrentTurn;
public Session(ActorContext<SessionMessage> context, List<ActorRef<ParticipantMessage>> participants, int totalTurns) {
this.context = context;
this.participants = participants;
this.totalTurns = totalTurns;
}
public void resetPot() {
this.currentPot = 0;
}
public void addToPot(double pot) {
currentPot += pot;
}
public void incrementParticipantsInTurn() {
this.numberOfParticipantsInCurrentTurn++;
}
public void resetNumberOfParticipantsInTurn() {
this.numberOfParticipantsInCurrentTurn = 0;
}
public int incrementCurrentTurnAndGet() {
return ++this.currentTurn;
}
public Behavior<SessionMessage> createSessionBehaviour() {
return Behaviors.receive(SessionMessage.class)
.onMessage(StartSession.class, startSession -> onSessionStarted(startSession.chatRoom()
, startSession.replyTo(), startSession.participants(), totalTurns))
.onMessage(PlayTurn.class, this::onPlayTurn)
.onMessage(EndSession.class, endSession -> onSessionEnded(participants))
.onMessage(ShareReturnPotWithParticipants.class,
sharePot -> onReturnPotToParticipants(sharePot.session(), sharePot.participants(), sharePot.returnedAmount()))
.build();
}
public static Behavior<SessionMessage> create(List<ActorRef<ParticipantMessage>> participants, int totalTurns) {
return Behaviors.setup(context -> new Session(context, participants, totalTurns)
.createSessionBehaviour());
}
private Behavior<SessionMessage> onPlayTurn(
PlayTurn playTurn) {
context.getLog()
.info("Participant {} joined for turn {} with {}",
playTurn.replyTo().path().name(),
playTurn.turn(),
playTurn.pot());
// Add to current pot
addToPot(playTurn.pot());
incrementParticipantsInTurn();
if (getNumberOfParticipantsInCurrentTurn() == participants.size()) {
double amountToShare = (getCurrentPot() * 2) / participants.size();
playTurn.session().narrow().tell(new ShareReturnPotWithParticipants(playTurn.session(), playTurn.participants(), amountToShare));
resetPot();
resetNumberOfParticipantsInTurn();
context.getLog().info("Turn {} complete", getCurrentTurn());
if (incrementCurrentTurnAndGet() == totalTurns) {
context.getLog().info("All turns completed - Waiting for other messages, then ending session.");
context.scheduleOnce(Duration.ofSeconds(3), playTurn.session().narrow(), new EndSession());
}
}
return Behaviors.same();
}
private Behavior<SessionMessage> onSessionEnded(List<ActorRef<ParticipantMessage>> participants) {
participants.forEach(participant -> participant.tell(new SessionEnded()));
return Behaviors.stopped();
}
private static Behavior<SessionMessage> onSessionStarted(
ActorRef<PotRoomMessage> chatRoom,
ActorRef<SessionMessage> session,
List<ActorRef<ParticipantMessage>> participants,
int totalTurns) {
participants.forEach(s -> s.tell(new SessionStarted(chatRoom, session, participants, totalTurns)));
return Behaviors.same();
}
private static Behavior<SessionMessage> onReturnPotToParticipants(
ActorRef<SessionMessage> session,
List<ActorRef<ParticipantMessage>> participants,
double returnedAmount) {
participants.forEach(participant -> participant.tell(new PotReturned(session, participant, returnedAmount)));
return Behaviors.same();
}
}

View File

@ -0,0 +1,42 @@
package dev.freireservices.social_altruism.chat.potroom;
import akka.actor.typed.ActorRef;
import dev.freireservices.social_altruism.chat.participant.ParticipantProtocol.ParticipantMessage;
import dev.freireservices.social_altruism.chat.potroom.PotRoomProtocol.PotRoomMessage;
import lombok.Getter;
import java.util.List;
@Getter
public class SessionProtocol {
public interface SessionMessage {
}
public record StartSession(
ActorRef<PotRoomMessage> chatRoom,
ActorRef<SessionMessage> replyTo,
List<ActorRef<ParticipantMessage>> participants)
implements SessionMessage {
}
public record PlayTurn(
ActorRef<SessionMessage> session,
ActorRef<ParticipantMessage> replyTo,
List<ActorRef<ParticipantMessage>> participants,
int turn,
double pot)
implements SessionMessage {
}
public record ShareReturnPotWithParticipants(
ActorRef<SessionMessage> session,
List<ActorRef<ParticipantMessage>> participants,
double returnedAmount) implements SessionMessage {
}
public record EndSession() implements SessionMessage { }
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="true"
xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="console" />
</root>
</log4j:configuration>

View File

@ -0,0 +1,57 @@
package dev.freireservices.social_altruism.chat;
import akka.actor.testkit.typed.javadsl.ActorTestKit;
import akka.actor.typed.ActorRef;
import dev.freireservices.social_altruism.chat.participant.Participant;
import dev.freireservices.social_altruism.chat.participant.ParticipantProtocol.ParticipantMessage;
import dev.freireservices.social_altruism.chat.potroom.PotRoom;
import dev.freireservices.social_altruism.chat.potroom.PotRoomProtocol;
import org.junit.AfterClass;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import static dev.freireservices.social_altruism.chat.participant.ParticipantType.*;
public class CaseStudiesTests {
public static final int INITIAL_COINS = 100;
public static final int TOTAL_PARTICIPANTS = 3;
public static final int TOTAL_TURNS = 100;
final static ActorTestKit testKit = ActorTestKit.create();
@Test
public void testCooperation() {
var potRoom = PotRoom.create(TOTAL_PARTICIPANTS, TOTAL_TURNS);
ActorRef<PotRoomProtocol.PotRoomMessage> chatRoomTest =
testKit.spawn(potRoom, "potRoom");
ActorRef<ParticipantMessage> p1 =
testKit.spawn(Participant.create(INITIAL_COINS, PICARO), "PICARO-1");
ActorRef<ParticipantMessage> p2 =
testKit.spawn(Participant.create(INITIAL_COINS, JUSTICIERO), "JUSTICIERO-1");
ActorRef<ParticipantMessage> p3 =
testKit.spawn(Participant.create(INITIAL_COINS, SANTO), "SANTO-1");
// Enter POT
chatRoomTest.tell(new PotRoomProtocol.EnterPot(p1));
chatRoomTest.tell(new PotRoomProtocol.EnterPot(p2));
chatRoomTest.tell(new PotRoomProtocol.EnterPot(p3));
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@AfterClass
public static void cleanup() {
testKit.shutdownTestKit();
}
}

View File

@ -0,0 +1,108 @@
package dev.freireservices.social_altruism.chat;
import akka.actor.testkit.typed.javadsl.ActorTestKit;
import akka.actor.testkit.typed.javadsl.BehaviorTestKit;
import akka.actor.testkit.typed.javadsl.TestProbe;
import akka.actor.typed.ActorRef;
import dev.freireservices.social_altruism.chat.participant.Participant;
import dev.freireservices.social_altruism.chat.participant.ParticipantProtocol;
import dev.freireservices.social_altruism.chat.participant.ParticipantProtocol.ParticipantMessage;
import dev.freireservices.social_altruism.chat.potroom.PotRoom;
import dev.freireservices.social_altruism.chat.potroom.PotRoomProtocol;
import org.junit.Test;
import java.time.Duration;
import static dev.freireservices.social_altruism.chat.participant.ParticipantType.*;
public class ProtocolTests {
public static final int INITIAL_COINS = 100;
public static final int TOTAL_PARTICIPANTS = 4;
@Test
public void testSessionStartedOnJoinParticipants() {
final ActorTestKit testKit = ActorTestKit.create();
TestProbe<ParticipantMessage> testProbe =
testKit.createTestProbe("TestProbe", ParticipantMessage.class);
var potRoom = PotRoom.create(TOTAL_PARTICIPANTS, 1);
ActorRef<PotRoomProtocol.PotRoomMessage> chatRoomTest =
testKit.spawn(potRoom, "potRoom");
BehaviorTestKit<PotRoomProtocol.PotRoomMessage> test = BehaviorTestKit.create(potRoom);
ActorRef<ParticipantMessage> p1 =
testKit.spawn(Participant.create(INITIAL_COINS, PICARO), "PICARO-1");
ActorRef<ParticipantMessage> p2 =
testKit.spawn(Participant.create(INITIAL_COINS, JUSTICIERO), "JUSTICIERO-1");
ActorRef<ParticipantMessage> p3 =
testKit.spawn(Participant.create(INITIAL_COINS, SANTO), "SANTO-1");
// Enter POT
chatRoomTest.tell(new PotRoomProtocol.EnterPot(p1));
chatRoomTest.tell(new PotRoomProtocol.EnterPot(p2));
chatRoomTest.tell(new PotRoomProtocol.EnterPot(p3));
chatRoomTest.tell(new PotRoomProtocol.EnterPot(testProbe.ref()));
//Session started
testProbe.expectMessageClass(ParticipantProtocol.SessionGranted.class, Duration.ofSeconds(20));
testProbe.expectMessageClass(ParticipantProtocol.SessionStarted.class, Duration.ofSeconds(20));
}
@Test
public void testActorGetsSessionGranted() {
final ActorTestKit testKit = ActorTestKit.create();
TestProbe<ParticipantMessage> testProbe =
testKit.createTestProbe("TestProbe");
ActorRef<PotRoomProtocol.PotRoomMessage> chatRoomTest =
testKit.spawn(PotRoom.create(1, 1), "chatRoom");
chatRoomTest.tell(new PotRoomProtocol.EnterPot(testProbe.ref()));
testProbe.expectMessageClass(ParticipantProtocol.SessionGranted.class, Duration.ofSeconds(10));
// #assert
}
@Test
public void testActorGetsSessionDenied() {
final ActorTestKit testKit = ActorTestKit.create();
TestProbe<ParticipantMessage> testProbe =
testKit.createTestProbe("TestProbe");
ActorRef<PotRoomProtocol.PotRoomMessage> chatRoomTest =
testKit.spawn(PotRoom.create(2, 1), "chatRoom");
chatRoomTest.tell(new PotRoomProtocol.EnterPot(testProbe.ref()));
chatRoomTest.tell(new PotRoomProtocol.EnterPot(testProbe.ref()));
testProbe.expectMessageClass(ParticipantProtocol.SessionDenied.class, Duration.ofSeconds(5));
// #assert
}
@Test
public void testMultipleSessions() {
final ActorTestKit testKit = ActorTestKit.create();
TestProbe<ParticipantMessage> testProbe =
testKit.createTestProbe("TestProbe");
ActorRef<PotRoomProtocol.PotRoomMessage> chatRoomTest =
testKit.spawn(PotRoom.create(2, 1), "chatRoom");
chatRoomTest.tell(new PotRoomProtocol.EnterPot(testProbe.ref()));
chatRoomTest.tell(new PotRoomProtocol.EnterPot(testProbe.ref()));
testProbe.expectMessageClass(ParticipantProtocol.SessionDenied.class, Duration.ofSeconds(5));
// #assert
}
}