Skip to content

Commit

Permalink
refactor EventFrame (#96)
Browse files Browse the repository at this point in the history
* merge_model_to_master

* pr before develop

* refactor: only keep eventframe related codes

* feat: impl event controller

* chore: rename event_frame to event_controller

---------

Co-authored-by: zyr17 <[email protected]>
  • Loading branch information
jiezichenku and zyr17 authored Feb 22, 2024
1 parent 3be319f commit c4222b7
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 221 deletions.
25 changes: 1 addition & 24 deletions src/lpsim/server/event.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from .struct import Cost, ObjectPosition
from .struct import Cost
from ..utils import BaseModel
from typing import Literal, List
from .consts import DieColor, ElementalReactionType, ElementType, ObjectType
from .action import (
ActionTypes,
ActionBase,
Actions,
DrawCardAction,
RestoreCardAction,
RemoveCardAction,
Expand Down Expand Up @@ -414,25 +413,3 @@ class UseCardEventArguments(EventArgumentsBase):
| CharacterReviveEventArguments
| UseCardEventArguments
)


class EventFrame(BaseModel):
"""
Event frame is a frame of events, contains events with their arguments, the
object lists that will be triggered by the event arguments, and the
action lists that has triggered.
When one action is done, it will generate a new event frame, with various
number of events, and append it to the event frame list. When there are
events and no processing objects and actions, the first event will
trigger objects. When there are triggered objects, the first object
position will be popped and trigger actions. When there are triggered
actions, the first action will be popped and trigger events, then add
a new event frame. If an event frame has no events, no triggered objects
and no triggered actions, it will be removed from the list.
"""

events: List[EventArguments]
processing_event: EventArguments | None = None
triggered_objects: List[ObjectPosition] = []
triggered_actions: List[Actions] = []
155 changes: 155 additions & 0 deletions src/lpsim/server/event_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import logging
from typing import Any, List

from .consts import ObjectPositionType
from .event import EventArguments
from .action import Actions
from .struct import ObjectPosition
from ..utils import BaseModel


class EventFrame(BaseModel):
"""
Event frame is a frame of events, contains events with their arguments, the
object lists that will be triggered by the event arguments, and the
action lists that has triggered.
When one action is done, it will generate a new event frame, with various
number of events, and append it to the event frame list. When there are
events and no processing objects and actions, the first event will
trigger objects. When there are triggered objects, the first object
position will be popped and trigger actions. When there are triggered
actions, the first action will be popped and trigger events, then add
a new event frame. If an event frame has no events, no triggered objects
and no triggered actions, it will be removed from the list.
"""

events: List[EventArguments]
processing_event: EventArguments | None = None
triggered_objects: List[ObjectPosition] = []
triggered_actions: List[Actions] = []


class EventController(BaseModel):
frame_list: List[EventFrame] = []

def has_event(self) -> bool:
return len(self.frame_list) != 0

def append(self, event_frame: EventFrame) -> None:
self.frame_list.append(event_frame)

def pop(self) -> EventFrame:
"""
pop and return last event frame.
"""
return self.frame_list.pop()

def run_event_frame(self, match: Any) -> None:
"""
run the event frame. if last event frame contains actions, do actions and
return; otherwise,
trigger next object; otherwise, trigger next event; otherwise, pop last frame;
otherwise, clear trashbin and done triggering.
When any actions has done, or no valid event frame exist, return.
"""
while len(self.frame_list):
event_frame = self.frame_list[-1]
if len(event_frame.triggered_actions):
self.act_action(event_frame, match)
return
elif len(event_frame.triggered_objects):
self.get_action(event_frame, match)
elif len(event_frame.events):
self.trigger_event(event_frame, match)
else:
self.pop()
# event frame cleared, clear trashbin
match.trashbin.clear()

def act_action(self, frame: EventFrame, match: Any) -> None:
"""
Act the actions. If these actions are triggered by one object (i.e.,
event_frame.processing_event is not None), do all actions immediately;
otherwise, actions are generated by system, and only do one action.
"""
if frame.processing_event is None:
# do one action
activated_action = frame.triggered_actions.pop(0)
logging.info(f"Action activated: {activated_action}")
event_args = match._act(activated_action)
match.record_last_action_history()
self.stack_events(event_args)
else:
# do all actions
event_args = []
for activated_action in frame.triggered_actions:
logging.info(f"Action activated: {activated_action}")
event_args += match._act(activated_action)
match.record_last_action_history()
frame.triggered_actions = []
self.stack_events(event_args)

def get_action(self, frame: EventFrame, match: Any) -> None:
"""
Get actions from triggered objects.
"""
event_arg = frame.processing_event
assert event_arg is not None
object_position = frame.triggered_objects.pop(0)
obj = match.get_object(object_position, event_arg.type)
handler_name = f"event_handler_{event_arg.type.name}"
func = getattr(obj, handler_name, None)
if func is not None:
frame.triggered_actions = func(event_arg, match)
self.frame_list[-1] = frame

def trigger_event(self, event_frame: EventFrame, match: Any) -> None:
"""
trigger new event to update triggered object lists of a EventFrame.
it will take first event from events, put it into processing_event,
and update triggered object lists.
"""
event_arg = event_frame.events.pop(0)
event_frame.processing_event = event_arg
object_list = match.get_object_list()
# add object in trashbin to list
for obj in match.trashbin:
if event_arg.type in obj.available_handler_in_trashbin:
object_list.append(obj)
handler_name = f"event_handler_{event_arg.type.name}"
for obj in object_list:
# for deck objects, check availability
if obj.position.area == ObjectPositionType.DECK:
if event_arg.type not in obj.available_handler_in_deck:
continue
func = getattr(obj, handler_name, None)
if func is not None:
event_frame.triggered_objects.append(obj.position)

def stack_event(self, event_arg: EventArguments) -> EventFrame:
"""
stack a new event. It will wrap it into a list and call
self.stack_events.
"""
return self.stack_events([event_arg])

def stack_actions(self, actions: List[Actions]) -> EventFrame:
"""
stack new actions, these actions are not triggered by any object, so when any
action in it is triggered, its event will be triggered immediately.
"""
event_frame = EventFrame(events=[], triggered_actions=actions)
self.frame_list.append(event_frame)
return event_frame

def stack_events(self, event_args: List[EventArguments]) -> EventFrame:
"""
stack events. It will create a new EventFrame with events and
append it into self.event_frames. Then it will return the event frame.
"""
frame = EventFrame(
events=event_args,
)
self.frame_list.append(frame)
return frame
Loading

0 comments on commit c4222b7

Please sign in to comment.