Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 193 additions & 0 deletions api/lib/src/event/event.mapper.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions api/lib/src/event/hybrid.dart
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ final class TeamRemoved extends HybridWorldEvent with TeamRemovedMappable {
TeamRemoved(this.team);
}

@MappableClass()
final class CellMergeStrategyChanged extends HybridWorldEvent
with CellMergeStrategyChangedMappable {
final GlobalVectorDefinition cell;
final CellMergeStrategy? strategy;
final int span;

CellMergeStrategyChanged(this.cell, this.strategy, {this.span = 1});
}

@MappableClass()
final class MetadataChanged extends HybridWorldEvent
with MetadataChangedMappable {
Expand Down
84 changes: 84 additions & 0 deletions api/lib/src/event/process/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ bool isValidServerEvent(ServerWorldEvent event, WorldState state) =>
.length -
1,
),
CellMergeStrategyChanged() =>
event.span > 0 && event.span <= GameTable.maxMergeSpan,
DialogOpened() => event.dialog.isValid(),
_ => true,
};
Expand Down Expand Up @@ -313,6 +315,88 @@ ServerProcessed processServerEvent(
teamMembers: Map.from(state.teamMembers)..remove(event.team),
),
);
case CellMergeStrategyChanged():
return ServerProcessed(
state.mapTableOrDefault(event.cell.table, (table) {
final cell = table.getCell(event.cell.position);
final cells = Map<VectorDefinition, TableCell>.from(table.cells);

CellMergeDirection? getDirection(CellMergeStrategy? strategy) {
if (strategy is LayoutCellMergeStrategy) return strategy.direction;
return null;
}

// Cleanup old neighbors
final oldCell = cells[event.cell.position];
final oldStrategy = oldCell?.merge;
final oldDirection = getDirection(oldStrategy);
if (oldDirection != null) {
final oldSpan = table.calculateSpan(
event.cell.position,
oldDirection,
);
if (oldSpan > 1) {
var current = event.cell.position;
for (var i = 1; i < oldSpan; i++) {
current = oldDirection == CellMergeDirection.horizontal
? VectorDefinition(current.x + 1, current.y)
: VectorDefinition(current.x, current.y + 1);

final neighbor = cells[current] ?? TableCell();
if (neighbor.merge is MergedCellStrategy &&
(neighbor.merge as MergedCellStrategy).direction ==
oldDirection) {
final updatedNeighbor = neighbor.copyWith(merge: null);
if (updatedNeighbor.isEmpty) {
cells.remove(current);
} else {
cells[current] = updatedNeighbor;
}
}
}
}
}
Comment thread
CodeDoctorDE marked this conversation as resolved.

// Update target cell
var newCell = cell.copyWith(merge: event.strategy);

// Expand new neighbors

final strategy = event.strategy;

final span = event.span;

Comment thread
CodeDoctorDE marked this conversation as resolved.
if (strategy != null) {
final direction = getDirection(strategy);

if (direction != null) {
var current = event.cell.position;
final allObjects = (cells[current] ?? TableCell()).objects
.toList();

for (var i = 1; i < span; i++) {
current = direction == CellMergeDirection.horizontal
? VectorDefinition(current.x + 1, current.y)
: VectorDefinition(current.x, current.y + 1);

final neighbor = cells[current] ?? TableCell();
if (neighbor.objects.isNotEmpty) {
allObjects.addAll(neighbor.objects);
}

cells[current] = neighbor.copyWith(
merge: MergedCellStrategy(direction),
objects: [],
);
}
Comment on lines +382 to +391
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When expanding neighbors you clear objects but leave tiles untouched. On the client, cells with MergedCellStrategy are hidden, so any tiles on those neighbor cells will effectively disappear while merged. Consider either migrating/merging tiles into the parent cell as well, or preventing merges across cells that contain tiles, or rendering tiles from merged children in the parent.

Copilot uses AI. Check for mistakes.
newCell = newCell.copyWith(objects: allObjects);
}
}
cells[event.cell.position] = newCell;

return table.copyWith.cellsBox(content: cells);
}),
);
case MetadataChanged():
return ServerProcessed(state.copyWith(metadata: event.metadata));
case MessageSent():
Expand Down
16 changes: 16 additions & 0 deletions api/lib/src/event/state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,20 @@ final class WorldState with WorldStateMappable {
String name,
GameTable Function(GameTable) mapper,
) => updateTable(name, mapper(getTableOrDefault(name)));

int calculateLocalSpan(
VectorDefinition start,
CellMergeDirection direction,
) => table.calculateSpan(start, direction);

int calculateSpan(
GlobalVectorDefinition start,
CellMergeDirection direction,
) => getTableOrDefault(start.table).calculateSpan(start.position, direction);

VectorDefinition getLocalParentCell(VectorDefinition start) =>
table.getParentCell(start);

VectorDefinition getParentCell(GlobalVectorDefinition position) =>
getTableOrDefault(position.table).getParentCell(position.position);
}
1 change: 1 addition & 0 deletions api/lib/src/event/state.mapper.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api/lib/src/helpers/equality.mapper.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api/lib/src/models/background.mapper.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading