Basic behavior ready

This commit is contained in:
Jaime Freire 2023-12-24 10:25:45 +01:00
parent 76e1781864
commit 6cb6de28fc
36 changed files with 277 additions and 134 deletions

View File

@ -11,8 +11,4 @@
Comprobar cómo de dispuesta está la gente a contribuir a un bien, que será compartido por todos por igual,
hayan contribuido o no.
-Un Chat genera una sesión para cada jugador.
-Cada chat tiene 2 o más participantes.
-Los chats tienen varios jugadores y ahí se juegan turnos.
hayan contribuido o no.

View File

@ -20,14 +20,13 @@
<dependencies>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-reload4j -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.14</version>
<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>

View File

@ -7,19 +7,23 @@ 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.commands.Commands;
import dev.freireservices.social_altruism.chat.events.Events;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
public class ChatPotProtocol {
private final ActorContext<RoomCommand> context;
private final ActorContext<Commands.RoomCommand> context;
private final List<ActorRef<SessionCommand>> sessions;
public double getCurrentPot() {
return currentPot;
}
public void setCurrentPot(double currentPot) {
this.currentPot = currentPot;
public void resetPot() {
this.currentPot = 0;
}
public void addToPot(double pot) {
@ -27,109 +31,135 @@ public class ChatPotProtocol {
}
private double currentPot = 0.0;
private int currentTurn = 0;
private final int numberOfParticipants;
private ChatPotProtocol(ActorContext<RoomCommand> context, int numberOfParticipants) {
this.numberOfParticipants = numberOfParticipants;
this.context = context;
public int getCurrentTurn() {
return currentTurn;
}
interface RoomCommand {}
public int incrementCurrentTurnAndGet() {
this.currentTurn++;
return this.currentTurn;
}
public record EnterPot(ActorRef<SessionEvent> replyTo, double pot) implements RoomCommand {}
private int currentTurn = 0;
private int totalTurns = 0;
// #chatroom-protocol
// #chatroom-behavior
private record UpdatePlayerAfterTurn(String screenName, String message) implements RoomCommand {}
public int getParticipantsInTurn() {
return participantsInTurn;
}
public void incrementParticipantsInTurn() {
this.participantsInTurn++;
}
public void resetParticipantsInTurn() {
this.participantsInTurn = 0;
}
public int getNumberOfParticipants() {
return numberOfParticipants;
}
private int participantsInTurn = 0;
private final int numberOfParticipants;
private ChatPotProtocol(
ActorContext<Commands.RoomCommand> context,
List<ActorRef<SessionCommand>> sessions,
int numberOfParticipants,
int turns) {
this.context = context;
this.sessions = sessions;
this.numberOfParticipants = numberOfParticipants;
this.totalTurns = turns;
}
// #chatroom-behavior
// #chatroom-protocol
private Behavior<RoomCommand> onGetPotSession(
EnterPot enterPot, List<ActorRef<SessionCommand>> sessions) {
//Add check session started
private Behavior<Commands.RoomCommand> onGetPotSession(Commands.EnterPot enterPot) {
// Add check session started
if (sessions.stream()
.anyMatch(
s ->
s.path().name().equals(URLEncoder.encode(enterPot.replyTo.path().name(), UTF_8)))) {
s.path()
.name()
.equals(URLEncoder.encode(enterPot.replyTo().path().name(), UTF_8)))) {
enterPot.replyTo.tell(new SessionDenied("Can only enter a pot once"));
enterPot.replyTo().tell(new Events.SessionDenied("Can only enter a pot once"));
}
context
.getLog()
.info("Participant joined {} turn pot: {}", enterPot.replyTo.path().name(), enterPot.pot);
context.getLog().info("Participant joined {} pot", enterPot.replyTo().path().name());
// Add to current pot
addToPot(enterPot.pot);
ActorRef<Events.SessionEvent> client = enterPot.replyTo();
ActorRef<SessionEvent> client = enterPot.replyTo;
ActorRef<SessionCommand> ses =
ActorRef<SessionCommand> session =
context.spawn(
Session.create(client), URLEncoder.encode(enterPot.replyTo.path().name(), UTF_8));
Session.create(client), URLEncoder.encode(enterPot.replyTo().path().name(), UTF_8));
// narrow to only expose PostMessage
client.tell(new SessionGranted(ses.narrow()));
client.tell(new Events.SessionGranted(session.narrow()));
List<ActorRef<SessionCommand>> newSessions = new ArrayList<>(sessions);
newSessions.add(ses);
if (numberOfParticipants == newSessions.size()) {
// Begin
context.getLog().info("All participants joined; beginning turn: " + getCurrentPot());
double amountToShare = (getCurrentPot() * 2) / numberOfParticipants;
context.getLog().info("Starting pot: " + getCurrentPot());
newSessions.forEach(s -> s.tell(new SharePotWithParticipants(amountToShare)));
currentTurn++;
return Behaviors.same();
sessions.add(session);
if (numberOfParticipants == sessions.size()) {
context.getLog().info("All participants joined; pot is ready to start.");
return createPotBehaviour();
} else {
// Waiting for more participants
context.getLog().info("Waiting for more participants.");
return createPot(newSessions);
return Behaviors.same();
}
}
private Behavior<RoomCommand> onUpdatePlayerAfterTurn(
List<ActorRef<SessionCommand>> sessions, UpdatePlayerAfterTurn pub) {
// NotifyClient notification = new NotifyClient((new MessagePosted(pub.screenName,
// pub.message)));
private Behavior<Commands.RoomCommand> onPlayTurn(Commands.PlayTurn playTurn) {
context
.getLog()
.info(
"Participant {} joined for turn {} with {}",
playTurn.replyTo().path().name(),
currentTurn,
playTurn.pot());
// Add to current pot
addToPot(playTurn.pot());
incrementParticipantsInTurn();
if (getParticipantsInTurn() == numberOfParticipants) {
double amountToShare = (getCurrentPot() * 2) / numberOfParticipants;
sessions.forEach(s -> s.tell(new SharePotWithParticipants(amountToShare)));
resetPot();
resetParticipantsInTurn();
context.getLog().info("Turn {} complete", getCurrentTurn());
if (incrementCurrentTurnAndGet() == totalTurns) {
context.getLog().info("All turns completed");
return Behaviors.stopped();
}
}
return Behaviors.same();
}
private Behavior<RoomCommand> createPot(List<ActorRef<SessionCommand>> sessions) {
return Behaviors.receive(RoomCommand.class)
.onMessage(EnterPot.class, enterPot -> onGetPotSession(enterPot, sessions))
//
.onMessage(UpdatePlayerAfterTurn.class, pub -> onUpdatePlayerAfterTurn(sessions, pub))
private Behavior<Commands.RoomCommand> createPotBehaviour() {
return Behaviors.receive(Commands.RoomCommand.class)
.onMessage(Commands.EnterPot.class, this::onGetPotSession)
.onMessage(Commands.PlayTurn.class, this::onPlayTurn)
.build();
}
public static Behavior<RoomCommand> create(int numberOfParticipants) {
return Behaviors.setup(ctx -> new ChatPotProtocol(ctx, numberOfParticipants).createPot(emptyList()));
public static Behavior<Commands.RoomCommand> create(int numberOfParticipants, int turns) {
return Behaviors.setup(
ctx ->
new ChatPotProtocol(ctx, new ArrayList<>(), numberOfParticipants, turns)
.createPotBehaviour());
}
public interface SessionEvent {}
public record SessionGranted(ActorRef<ParticipateInTurn> handle) implements SessionEvent {}
public record SessionDenied(String reason) implements SessionEvent {}
public record MessagePosted(String screenName, String message) implements SessionEvent {}
public record PotReturned(double returnedAmount) implements SessionEvent {}
interface SessionCommand {}
public record ParticipateInTurn(String message) implements SessionCommand {}
@ -137,7 +167,7 @@ public class ChatPotProtocol {
public record SharePotWithParticipants(double returnedAmount) implements SessionCommand {}
static class Session {
static Behavior<ChatPotProtocol.SessionCommand> create(ActorRef<SessionEvent> client) {
static Behavior<ChatPotProtocol.SessionCommand> create(ActorRef<Events.SessionEvent> client) {
return Behaviors.receive(ChatPotProtocol.SessionCommand.class)
.onMessage(
SharePotWithParticipants.class,
@ -146,8 +176,8 @@ public class ChatPotProtocol {
}
private static Behavior<SessionCommand> onSharePotWithParticipants(
ActorRef<SessionEvent> client, double returnedAmount) {
client.tell(new ChatPotProtocol.PotReturned(returnedAmount));
ActorRef<Events.SessionEvent> participant, double returnedAmount) {
participant.tell(new Events.PotReturned(participant, returnedAmount));
return Behaviors.same();
}
}

View File

@ -5,6 +5,8 @@ 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.commands.Commands;
import dev.freireservices.social_altruism.chat.events.Events;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
@ -19,25 +21,45 @@ public class ChatQuickStart {
public static class Main {
static List<ActorRef<ChatPotProtocol.SessionEvent>> sessions = new ArrayList<>();
static List<ActorRef<Events.SessionEvent>> sessions = new ArrayList<>();
static final int numberOfParticipants = 2;
static final int numberOfTurns = 100;
public static Behavior<Void> create() {
return Behaviors.setup(
context -> {
ActorRef<ChatPotProtocol.RoomCommand> chatRoom = context.spawn(ChatPotProtocol.create(100), "potRoom");
ActorRef<Commands.RoomCommand> chatRoom =
context.spawn(
ChatPotProtocol.create(numberOfParticipants, numberOfTurns), "potRoom");
// Agregamos jugadores
sessions.addAll(
IntStream.range(0, 100)
IntStream.range(0, numberOfParticipants)
.mapToObj(
i ->
context.spawn(
Participante.create(getRandomNumberBetween(0, 100)), "participante-" + i))
Participante.create(getRandomNumberBetween(0, 100)),
"participante-" + i))
.toList());
sessions.forEach(s -> chatRoom.tell(new ChatPotProtocol.EnterPot(s, getRandomNumberBetween(0, 100))));
// Entrar en pot
for (ActorRef<Events.SessionEvent> session : sessions) {
chatRoom.tell(new Commands.EnterPot(session));
}
for (int i = 0; i < numberOfTurns; i++) {
// Participar en cada turno cantidad diferente
for (ActorRef<Events.SessionEvent> session : sessions) {
// Pícaro
if (session.path().name().contains("participante-0")) {
chatRoom.tell(new Commands.PlayTurn(session, 0));
} else {
chatRoom.tell(new Commands.PlayTurn(session, getRandomNumberBetween(0, 10)));
}
}
}
return Behaviors.receive(Void.class)
.onSignal(Terminated.class, sig -> Behaviors.stopped())

View File

@ -3,14 +3,15 @@ package dev.freireservices.social_altruism.chat;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;
import dev.freireservices.social_altruism.chat.events.Events;
public class Participante {
public static Behavior<ChatPotProtocol.SessionEvent> create(int monedasInit) {
public static Behavior<Events.SessionEvent> create(int monedasInit) {
return Behaviors.setup(ctx -> new Participante(ctx, monedasInit).behavior());
}
private final ActorContext<ChatPotProtocol.SessionEvent> context;
private final ActorContext<Events.SessionEvent> context;
public double getMonedas() {
return monedas;
@ -30,43 +31,55 @@ public class Participante {
private double monedas;
private Participante(ActorContext<ChatPotProtocol.SessionEvent> context, double monedas) {
this.context = context;
this.monedas = monedas;
public double getMonedasInit() {
return monedasInit;
}
private Behavior<ChatPotProtocol.SessionEvent> behavior() {
return Behaviors.receive(ChatPotProtocol.SessionEvent.class)
.onMessage(ChatPotProtocol.SessionDenied.class, this::onSessionDenied)
.onMessage(ChatPotProtocol.SessionGranted.class, this::onSessionGranted)
.onMessage(ChatPotProtocol.MessagePosted.class, this::onMessagePosted)
.onMessage(ChatPotProtocol.PotReturned.class, this::onPotReturned)
private final double monedasInit;
private Participante(ActorContext<Events.SessionEvent> context, double monedas) {
this.context = context;
this.monedas = monedas;
this.monedasInit = monedas;
}
private Behavior<Events.SessionEvent> behavior() {
return Behaviors.receive(Events.SessionEvent.class)
.onMessage(Events.SessionDenied.class, this::onSessionDenied)
.onMessage(Events.SessionGranted.class, this::onSessionGranted)
.onMessage(Events.PotReturned.class, this::onPotReturned)
.build();
}
private Behavior<ChatPotProtocol.SessionEvent> onSessionDenied(
ChatPotProtocol.SessionDenied message) {
private Behavior<Events.SessionEvent> onSessionDenied(Events.SessionDenied message) {
context.getLog().info("cannot start chat room session: {}", message.reason());
return Behaviors.stopped();
}
private Behavior<ChatPotProtocol.SessionEvent> onSessionGranted(
ChatPotProtocol.SessionGranted message) {
private Behavior<Events.SessionEvent> onSessionGranted(Events.SessionGranted message) {
return Behaviors.same();
}
private Behavior<ChatPotProtocol.SessionEvent> onMessagePosted(
ChatPotProtocol.MessagePosted message) {
context
.getLog()
.info("message has been posted by '{}': {}", message.screenName(), message.message());
return Behaviors.same();
}
private Behavior<ChatPotProtocol.SessionEvent> onPotReturned(
ChatPotProtocol.PotReturned potReturned) {
private Behavior<Events.SessionEvent> onPotReturned(Events.PotReturned potReturned) {
context.getLog().info("Pot returned: {}", potReturned.returnedAmount());
incrementMonedas(potReturned.returnedAmount());
context
.getLog()
.info(
"Player {} has now {} coins; started with {} for a total profit of: {} %",
potReturned.participant().path().name(),
getMonedas(),
getMonedasInit(),
calculateProfit());
//Calcular contribución total.
//Si detecta baja contribución aplicar penalización
return Behaviors.same();
}
private double calculateProfit() {
return Math.round((getMonedas() * 100) / getMonedasInit() -100);
}
}

View File

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

View File

@ -0,0 +1,15 @@
package dev.freireservices.social_altruism.chat.commands;
import akka.actor.typed.ActorRef;
import dev.freireservices.social_altruism.chat.events.Events;
public class Commands {
public interface RoomCommand {}
public record EnterPot(ActorRef<Events.SessionEvent> replyTo) implements RoomCommand {}
public record PlayTurn(ActorRef<Events.SessionEvent> replyTo, double pot) implements RoomCommand {}
// #chatroom-protocol
// #chatroom-behavior
}

View File

@ -0,0 +1,16 @@
package dev.freireservices.social_altruism.chat.events;
import akka.actor.typed.ActorRef;
import dev.freireservices.social_altruism.chat.ChatPotProtocol;
public class Events {
public interface SessionEvent {}
public record SessionGranted(ActorRef<ChatPotProtocol.ParticipateInTurn> handle) implements SessionEvent {}
public record SessionDenied(String reason) implements SessionEvent {}
public record PotReturned(ActorRef participant, double returnedAmount) implements SessionEvent {}
}

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

@ -3,6 +3,8 @@ package dev.freireservices.social_altruism.chat;
import akka.actor.testkit.typed.javadsl.ActorTestKit;
import akka.actor.testkit.typed.javadsl.TestProbe;
import akka.actor.typed.ActorRef;
import dev.freireservices.social_altruism.chat.commands.Commands;
import dev.freireservices.social_altruism.chat.events.Events;
import java.time.Duration;
import org.junit.Test;
@ -13,35 +15,41 @@ public class ChatQuickStartTest {
// #test
@Test
// FIXME - Improve or delete..
public void testCooperationCaseOne() {
final ActorTestKit testKit = ActorTestKit.create();
TestProbe<ChatPotProtocol.SessionEvent> testProbe = testKit.createTestProbe();
TestProbe<Events.SessionEvent> testProbe = testKit.createTestProbe();
ActorRef<ChatPotProtocol.RoomCommand> chatRoomTest =
testKit.spawn(ChatPotProtocol.create(2), "chatRoom");
ActorRef<Commands.RoomCommand> chatRoomTest =
testKit.spawn(ChatPotProtocol.create(2, 1), "chatRoom");
ActorRef<ChatPotProtocol.SessionEvent> participanteUno =
ActorRef<Events.SessionEvent> participanteUno =
testKit.spawn(Participante.create(100), "participanteUno");
ActorRef<ChatPotProtocol.SessionEvent> participanteDos =
ActorRef<Events.SessionEvent> participanteDos =
testKit.spawn(Participante.create(10), "participanteDos");
ActorRef<ChatPotProtocol.SessionEvent> participanteTres =
testKit.spawn(Participante.create(100), "participanteTres");
ActorRef<Events.SessionEvent> participanteTres =
testKit.spawn(Participante.create(100), "participanteTres");
ActorRef<ChatPotProtocol.SessionEvent> participanteCuatro =
testKit.spawn(Participante.create(10), "participanteCuatro");
ActorRef<Events.SessionEvent> participanteCuatro =
testKit.spawn(Participante.create(10), "participanteCuatro");
// Enter POT
chatRoomTest.tell(new Commands.EnterPot(participanteUno));
chatRoomTest.tell(new Commands.EnterPot(participanteDos));
chatRoomTest.tell(new Commands.EnterPot(participanteTres));
chatRoomTest.tell(new Commands.EnterPot(participanteCuatro));
// Turnos
chatRoomTest.tell(new ChatPotProtocol.EnterPot(participanteUno, 10));
chatRoomTest.tell(new ChatPotProtocol.EnterPot(participanteDos, 1));
chatRoomTest.tell(new ChatPotProtocol.EnterPot(participanteTres, 11));
chatRoomTest.tell(new ChatPotProtocol.EnterPot(participanteCuatro, 2));
chatRoomTest.tell(new Commands.PlayTurn(participanteUno, 1));
chatRoomTest.tell(new Commands.PlayTurn(participanteDos, 2));
chatRoomTest.tell(new Commands.PlayTurn(participanteTres, 3));
chatRoomTest.tell(new Commands.PlayTurn(participanteCuatro, 4));
// #assert
// #assert
@ -51,19 +59,19 @@ public class ChatQuickStartTest {
@Test
public void testActorGetsUserDenied() {
final ActorTestKit testKit = ActorTestKit.create();
TestProbe<ChatPotProtocol.SessionEvent> testProbe = testKit.createTestProbe();
TestProbe<Events.SessionEvent> testProbe = testKit.createTestProbe();
ActorRef<ChatPotProtocol.RoomCommand> chatRoomTest =
testKit.spawn(ChatPotProtocol.create(2), "chatRoom");
ActorRef<Commands.RoomCommand> chatRoomTest =
testKit.spawn(ChatPotProtocol.create(2, 1), "chatRoom");
chatRoomTest.tell(new ChatPotProtocol.EnterPot(testProbe.ref(), 10));
chatRoomTest.tell(new Commands.EnterPot(testProbe.ref()));
testProbe.expectMessageClass(ChatPotProtocol.SessionGranted.class, Duration.ofSeconds(10));
testProbe.expectMessageClass(Events.SessionGranted.class, Duration.ofSeconds(10));
chatRoomTest.tell(new ChatPotProtocol.EnterPot(testProbe.ref(), 10));
chatRoomTest.tell(new Commands.EnterPot(testProbe.ref()));
ChatPotProtocol.SessionDenied sessionDenied =
new ChatPotProtocol.SessionDenied("Can only enter a pot once");
Events.SessionDenied sessionDenied =
new Events.SessionDenied("Can only enter a pot once");
testProbe.expectMessage(Duration.ofSeconds(10), sessionDenied);

18
target/classes/log4j.xml Normal file
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>