[common] use proto types

This commit is contained in:
2020-05-28 20:42:41 +03:00
parent b7b1ac3871
commit fec7680eee
36 changed files with 514 additions and 377 deletions

View File

@@ -2,8 +2,10 @@ package ru.m.data;
import flash.net.SharedObject; import flash.net.SharedObject;
import haxe.DynamicAccess; import haxe.DynamicAccess;
import haxe.io.Bytes;
import haxe.Serializer; import haxe.Serializer;
import haxe.Unserializer; import haxe.Unserializer;
import hw.connect.PacketUtil;
import promhx.Promise; import promhx.Promise;
import ru.m.data.IDataSource; import ru.m.data.IDataSource;
@@ -34,6 +36,15 @@ class DefaultConverter<D> extends Converter<D, String> {
} }
} }
class ProtoConverter<D:protohx.Message> extends Converter<D, String> {
public function new(messageClass:Class<D>) {
super(
item -> PacketUtil.toBytes(item).toHex(),
data -> PacketUtil.fromBytes(Bytes.ofHex(data), messageClass)
);
}
}
class EmptyConverter<T> extends Converter<T, T> { class EmptyConverter<T> extends Converter<T, T> {
public function new() { public function new() {
super(item -> item, data -> data); super(item -> item, data -> data);

View File

@@ -1,17 +1,18 @@
package ru.m.puzzlez.net; package ru.m.puzzlez.net;
import haxe.Unserializer;
import hw.connect.ConnectionFactory; import hw.connect.ConnectionFactory;
import hw.connect.IConnection; import hw.connect.IConnection;
import hw.signal.Signal; import hw.signal.Signal;
import hw.storage.SharedObjectStorage; import hw.storage.SharedObjectStorage;
import promhx.Promise; import promhx.Promise;
import ru.m.data.IDataSource; import ru.m.data.IDataSource;
import ru.m.puzzlez.core.GameEvent;
import ru.m.puzzlez.core.GameState;
import ru.m.puzzlez.core.Id; import ru.m.puzzlez.core.Id;
import ru.m.puzzlez.proto.game.GameProto; import ru.m.puzzlez.proto.core.User;
import ru.m.puzzlez.proto.core.UserProto; import ru.m.puzzlez.proto.event.GameAction;
import ru.m.puzzlez.proto.event.GameEvent;
import ru.m.puzzlez.proto.game.GameItem;
import ru.m.puzzlez.proto.game.GameState;
import ru.m.puzzlez.proto.pack.GameActionRequest;
import ru.m.puzzlez.proto.pack.GameCreateRequest; import ru.m.puzzlez.proto.pack.GameCreateRequest;
import ru.m.puzzlez.proto.pack.GameJoinRequest; import ru.m.puzzlez.proto.pack.GameJoinRequest;
import ru.m.puzzlez.proto.pack.LoginRequest; import ru.m.puzzlez.proto.pack.LoginRequest;
@@ -19,14 +20,14 @@ import ru.m.puzzlez.proto.pack.Request;
import ru.m.puzzlez.proto.pack.Response; import ru.m.puzzlez.proto.pack.Response;
@:provide class Network implements IDataIndex<ImageId> { @:provide class Network implements IDataIndex<ImageId> {
public var user(default, null):UserProto; public var user(default, null):User;
public var userSignal(default, null):Signal<UserProto> = new Signal(); public var userSignal(default, null):Signal<User> = new Signal();
public var gameList(default, null):Array<GameProto>; public var gameList(default, null):Array<GameItem>;
public var gameListSignal(default, null):Signal<Array<GameProto>> = new Signal(); public var gameListSignal(default, null):Signal<Array<GameItem>> = new Signal();
public var game(default, null):GameProto; public var game(default, null):GameItem;
public var gameSignal(default, null):Signal<GameProto> = new Signal(); public var gameSignal(default, null):Signal<GameItem> = new Signal();
public var gameEventSignal(default, null):Signal<GameEvent> = new Signal(); public var gameEventSignal(default, null):Signal<GameEvent> = new Signal();
@@ -37,11 +38,11 @@ import ru.m.puzzlez.proto.pack.Response;
public function new() { public function new() {
gameList = []; gameList = [];
storage = new SharedObjectStorage("network"); storage = new SharedObjectStorage("network/2");
if (storage.exists(USER_KEY)) { if (storage.exists(USER_KEY)) {
user = storage.read(USER_KEY); user = storage.read(USER_KEY);
} else { } else {
user = new UserProto().setName('Anonimus #${IdUtil.generate()}'); user = new User().setName('Anonimus #${IdUtil.generate()}');
storage.write(USER_KEY, user); storage.write(USER_KEY, user);
} }
connection = ConnectionFactory.buildClientConnection("127.0.0.1", 5000, Response); connection = ConnectionFactory.buildClientConnection("127.0.0.1", 5000, Response);
@@ -63,8 +64,7 @@ import ru.m.puzzlez.proto.pack.Response;
} }
public function sendGameAction(action:GameAction):Void { public function sendGameAction(action:GameAction):Void {
// ToDo: send action connection.send(new Request().setGameAction(new GameActionRequest().setActions([action])));
//connection.send(new Request().setGameEvent());
} }
private function onConnectionChange(event:ConnectionEvent):Void { private function onConnectionChange(event:ConnectionEvent):Void {

View File

@@ -1,9 +1,11 @@
package ru.m.puzzlez.net; package ru.m.puzzlez.net;
import hw.signal.Signal; import hw.signal.Signal;
import ru.m.puzzlez.core.GameState;
import ru.m.puzzlez.core.GameEvent;
import ru.m.puzzlez.core.IGame; import ru.m.puzzlez.core.IGame;
import ru.m.puzzlez.proto.event.GameAction;
import ru.m.puzzlez.proto.event.GameEvent;
import ru.m.puzzlez.proto.game.GameState;
import ru.m.puzzlez.proto.game.GameStatus;
class NetworkGame implements IGame { class NetworkGame implements IGame {
public var state(default, null):GameState; public var state(default, null):GameState;
@@ -23,7 +25,7 @@ class NetworkGame implements IGame {
public function start():Void { public function start():Void {
network.gameEventSignal.connect(onEvent); network.gameEventSignal.connect(onEvent);
switch state.status { switch state.status {
case READY: case GameStatus.READY:
network.startGame(state); network.startGame(state);
case _: case _:
network.joinGame(state); network.joinGame(state);

View File

@@ -1,8 +1,9 @@
package ru.m.puzzlez.render; package ru.m.puzzlez.render;
import ru.m.puzzlez.wrap.RectangleExt;
import ru.m.puzzlez.proto.game.GamePreset;
import flash.display.Shape; import flash.display.Shape;
import flash.geom.Matrix; import flash.geom.Matrix;
import ru.m.puzzlez.core.GamePreset;
import ru.m.puzzlez.render.part.IPartBuilder; import ru.m.puzzlez.render.part.IPartBuilder;
class CompleteView extends Shape { class CompleteView extends Shape {
@@ -29,8 +30,8 @@ class CompleteView extends Shape {
return; return;
} }
var partWidth = preset.imageRect.width / preset.grid.width; var partWidth = preset.imageRect.width / preset.grid.x;
var partHeight = preset.imageRect.height / preset.grid.height; var partHeight = preset.imageRect.height / preset.grid.y;
graphics.clear(); graphics.clear();
graphics.lineStyle(2, 0xCCCCCC); graphics.lineStyle(2, 0xCCCCCC);
@@ -51,9 +52,9 @@ class CompleteView extends Shape {
graphics.endFill(); graphics.endFill();
} }
var rect = part.rect.clone(); var rect = cast(part.rect, RectangleExt).clone();
rect.x = part.gridX * part.rect.width; rect.x = part.point.x * part.rect.width;
rect.y = part.gridY * part.rect.height; rect.y = part.point.y * part.rect.height;
var path = builder.build(rect, part.bounds); var path = builder.build(rect, part.bounds);
for (value in RenderUtil.borderSettings) { for (value in RenderUtil.borderSettings) {
graphics.lineStyle(1, value.color, value.opacity); graphics.lineStyle(1, value.color, value.opacity);

View File

@@ -2,7 +2,8 @@ package ru.m.puzzlez.render;
import hw.signal.Signal; import hw.signal.Signal;
import hw.view.IView; import hw.view.IView;
import ru.m.puzzlez.core.GameEvent; import ru.m.puzzlez.proto.event.GameAction;
import ru.m.puzzlez.proto.event.GameEvent;
interface IRender extends IView<Dynamic> { interface IRender extends IView<Dynamic> {
public var actions(default, null):Signal<GameAction>; public var actions(default, null):Signal<GameAction>;

View File

@@ -1,11 +1,11 @@
package ru.m.puzzlez.render; package ru.m.puzzlez.render;
import ru.m.puzzlez.render.RenderUtil;
import flash.display.BitmapData; import flash.display.BitmapData;
import haxe.Timer; import haxe.Timer;
import promhx.PublicStream; import promhx.PublicStream;
import promhx.Stream; import promhx.Stream;
import ru.m.puzzlez.core.Part; import ru.m.puzzlez.proto.game.Part;
import ru.m.puzzlez.render.RenderUtil;
typedef Result = { typedef Result = {
var part:Part; var part:Part;

View File

@@ -1,14 +1,16 @@
package ru.m.puzzlez.render; package ru.m.puzzlez.render;
import flash.display.BitmapData;
import flash.display.Bitmap; import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.PixelSnapping; import flash.display.PixelSnapping;
import flash.display.Sprite; import flash.display.Sprite;
import hw.geom.Point;
import ru.m.puzzlez.core.Part;
import ru.m.puzzlez.core.Id; import ru.m.puzzlez.core.Id;
import ru.m.puzzlez.render.RenderUtil; import ru.m.puzzlez.proto.core.Point;
import ru.m.puzzlez.proto.game.Part;
import ru.m.puzzlez.render.part.IPartBuilder; import ru.m.puzzlez.render.part.IPartBuilder;
import ru.m.puzzlez.render.RenderUtil;
import ru.m.puzzlez.wrap.PointExt;
import ru.m.puzzlez.wrap.RectangleExt;
class PartView extends Sprite { class PartView extends Sprite {
@@ -19,7 +21,7 @@ class PartView extends Sprite {
public var target(default, null):Point; public var target(default, null):Point;
private function set_position(value:Point):Point { private function set_position(value:Point):Point {
position = value.clone(); position = cast(value, PointExt).clone();
refresh(); refresh();
return position; return position;
} }
@@ -58,8 +60,8 @@ class PartView extends Sprite {
super(); super();
this.id = part.id; this.id = part.id;
this.part = part; this.part = part;
this.size = part.rect.size.clone(); this.size = cast(part.rect, RectangleExt).size;
this.target = new Point(part.gridX * size.x, part.gridY * size.y); this.target = new Point().setX(part.point.x * size.x).setY(part.point.y * size.y);
} }
private function redraw():Void { private function redraw():Void {
@@ -101,7 +103,7 @@ class SpritePartView extends PartView {
graphics.endFill(); graphics.endFill();
if (playerId != null) { if (playerId != null) {
var rect = part.rect.clone(); var rect = cast(part.rect, RectangleExt).clone();
rect.x += (image.width - size.x) / 2; rect.x += (image.width - size.x) / 2;
rect.y += (image.height - size.y) / 2; rect.y += (image.height - size.y) / 2;
var path = builder.build(rect, part.bounds); var path = builder.build(rect, part.bounds);

View File

@@ -1,7 +1,5 @@
package ru.m.puzzlez.render; package ru.m.puzzlez.render;
import ru.m.puzzlez.core.Id;
import ru.m.puzzlez.net.Network;
import flash.display.BitmapData; import flash.display.BitmapData;
import flash.display.PNGEncoderOptions; import flash.display.PNGEncoderOptions;
import flash.display.Sprite; import flash.display.Sprite;
@@ -10,16 +8,20 @@ import flash.geom.Point as FlashPoint;
import flash.geom.Rectangle; import flash.geom.Rectangle;
import flash.net.FileReference; import flash.net.FileReference;
import flash.utils.ByteArray; import flash.utils.ByteArray;
import hw.geom.Point;
import hw.signal.Signal; import hw.signal.Signal;
import hw.view.popup.AlertView; import hw.view.popup.AlertView;
import hw.view.SpriteView; import hw.view.SpriteView;
import ru.m.puzzlez.core.GameEvent; import ru.m.puzzlez.core.Id;
import ru.m.puzzlez.core.GameState; import ru.m.puzzlez.net.Network;
import ru.m.puzzlez.core.PartLocation; import ru.m.puzzlez.proto.event.gameaction.Action;
import ru.m.puzzlez.proto.event.GameAction;
import ru.m.puzzlez.proto.event.GameEvent;
import ru.m.puzzlez.proto.game.GameState;
import ru.m.puzzlez.proto.game.PartLocation;
import ru.m.puzzlez.render.ImagePartBuilder; import ru.m.puzzlez.render.ImagePartBuilder;
import ru.m.puzzlez.storage.ImageStorage; import ru.m.puzzlez.storage.ImageStorage;
import ru.m.puzzlez.storage.SettingsStorage; import ru.m.puzzlez.storage.SettingsStorage;
import ru.m.puzzlez.wrap.PointExt;
class Render extends SpriteView implements IRender { class Render extends SpriteView implements IRender {
@@ -33,7 +35,7 @@ class Render extends SpriteView implements IRender {
private var playerId(get, never):PlayerId; private var playerId(get, never):PlayerId;
private function get_playerId():PlayerId { private function get_playerId():PlayerId {
return network.user.uuid; return network.user.hasUuid() ? network.user.uuid : "local";
} }
private function get_scale():Float { private function get_scale():Float {
@@ -57,7 +59,7 @@ class Render extends SpriteView implements IRender {
private var imageView:CompleteView; private var imageView:CompleteView;
private var parts:Map<Int, PartView>; private var parts:Map<Int, PartView>;
private var activePart:PartView; private var activePart:PartView;
private var activePoint:Point; private var activePoint:PointExt;
private var movePoint:FlashPoint; private var movePoint:FlashPoint;
@@ -79,25 +81,21 @@ class Render extends SpriteView implements IRender {
} }
public function onGameEvent(event:GameEvent):Void { public function onGameEvent(event:GameEvent):Void {
switch event { if (event.hasStart()) {
case START(state, resume): onStart(event.start.state);
onStart(state); } else if (event.hasChange()) {
case CHANGE(PART_UPDATE(id, TABLE(point))): var part:PartView = parts[event.change.partId];
var part:PartView = parts[id]; part.playerId = event.change.playerId;
part.position = point; part.position = event.change.position;
part.playerId = null; switch event.change.location {
case CHANGE(PART_UPDATE(id, HAND(playerId, point))): case PartLocation.IMAGE:
var part:PartView = parts[id]; part.complete();
part.position = point; imageView.addChild(part);
part.playerId = playerId; imageView.redraw();
case CHANGE(PART_UPDATE(id, IMAGE)): case _:
var part:PartView = parts[id]; }
part.complete(); } else if (event.hasComplete()) {
imageView.addChild(part); AlertView.alert("Complete!");
imageView.redraw();
case COMPLETE:
AlertView.alert("Complete!");
case _:
} }
} }
@@ -108,10 +106,10 @@ class Render extends SpriteView implements IRender {
var partView = PartView.factory(part); var partView = PartView.factory(part);
parts.set(part.id, partView); parts.set(part.id, partView);
switch part.location { switch part.location {
case TABLE(point): case PartLocation.TABLE:
partView.position = point; partView.position = part.position;
tableView.addChild(partView); tableView.addChild(partView);
case IMAGE: case PartLocation.IMAGE:
partView.complete(); partView.complete();
imageView.addChild(partView); imageView.addChild(partView);
case _: case _:
@@ -200,24 +198,38 @@ class Render extends SpriteView implements IRender {
} }
activePart = pointPart; activePart = pointPart;
tableView.setChildIndex(activePart, tableView.numChildren - 1); tableView.setChildIndex(activePart, tableView.numChildren - 1);
activePoint = RenderUtil.convertPoint(tableView.globalToLocal(point)); activePoint = tableView.globalToLocal(point);
tableView.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); tableView.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
tableView.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); tableView.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
actions.emit(PART_TAKE(playerId, activePart.id)); actions.emit(new GameAction()
.setAction(Action.TAKE)
.setPlayerId(playerId)
.setPartId(activePart.id)
);
} }
} }
private function onMouseMove(event:MouseEvent):Void { private function onMouseMove(event:MouseEvent):Void {
var newPoint:Point = RenderUtil.convertPoint(tableView.globalToLocal(new FlashPoint(event.stageX, event.stageY))); var newPoint:PointExt = tableView.globalToLocal(new FlashPoint(event.stageX, event.stageY));
var partPosition = activePart.position.add(newPoint).subtract(activePoint); var partPosition = cast(activePart.position, PointExt).add(newPoint).subtract(activePoint);
actions.emit(PART_MOVE(playerId, activePart.id, partPosition.clone())); actions.emit(new GameAction()
.setAction(Action.MOVE)
.setPlayerId(playerId)
.setPartId(activePart.id)
.setPosition(partPosition)
);
activePoint = newPoint; activePoint = newPoint;
} }
private function onMouseUp(event:MouseEvent):Void { private function onMouseUp(event:MouseEvent):Void {
var newPoint:Point = RenderUtil.convertPoint(tableView.globalToLocal(new FlashPoint(event.stageX, event.stageY))); var newPoint:PointExt = tableView.globalToLocal(new FlashPoint(event.stageX, event.stageY));
var partPosition = activePart.position.add(newPoint).subtract(activePoint); var partPosition = cast(activePart.position, PointExt).add(newPoint).subtract(activePoint);
actions.emit(PART_PUT(playerId, activePart.id, partPosition.clone())); actions.emit(new GameAction()
.setAction(Action.PUT)
.setPlayerId(playerId)
.setPartId(activePart.id)
.setPosition(partPosition)
);
tableView.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); tableView.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
tableView.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); tableView.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
activePart = null; activePart = null;

View File

@@ -4,12 +4,13 @@ import flash.display.BitmapData;
import flash.display.Shape; import flash.display.Shape;
import flash.geom.Matrix; import flash.geom.Matrix;
import hw.color.Color; import hw.color.Color;
import hw.geom.Point;
import hw.geom.Rectangle;
import ru.m.draw.DrawPath; import ru.m.draw.DrawPath;
import ru.m.puzzlez.core.Part; import ru.m.puzzlez.proto.core.Point;
import ru.m.puzzlez.proto.core.Rectangle;
import ru.m.puzzlez.proto.game.Part;
import ru.m.puzzlez.render.part.IPartBuilder; import ru.m.puzzlez.render.part.IPartBuilder;
import ru.m.puzzlez.render.part.PartMask; import ru.m.puzzlez.render.part.PartMask;
import ru.m.puzzlez.wrap.RectangleExt;
typedef DrawSetting = { typedef DrawSetting = {
var offset:Point; var offset:Point;
@@ -24,13 +25,13 @@ typedef PartImage = {
class RenderUtil { class RenderUtil {
public static var shadowSettings(default, null):Array<DrawSetting> = [ public static var shadowSettings(default, null):Array<DrawSetting> = [
{offset: new Point(-2, -2), color: 0x000000, opacity: 0.4}, {offset: new Point().setX(-2).setY(-2), color: 0x000000, opacity: 0.4},
{offset: new Point(2, 2), color: 0xffffff, opacity: 0.4}, {offset: new Point().setX(2).setY(2), color: 0xffffff, opacity: 0.4},
]; ];
public static var borderSettings(default, null):Array<DrawSetting> = [ public static var borderSettings(default, null):Array<DrawSetting> = [
{offset: new Point(-1, -1), color: 0x555555, opacity: 0.4}, {offset: new Point().setX(-1).setY(-1), color: 0x555555, opacity: 0.4},
{offset: new Point(1, 1), color: 0xcccccc, opacity: 0.4}, {offset: new Point().setX(1).setY(1), color: 0xcccccc, opacity: 0.4},
]; ];
@:provide static var builder:IPartBuilder; @:provide static var builder:IPartBuilder;
@@ -46,14 +47,18 @@ class RenderUtil {
} }
private static function buildPartGeometry(part:Part):{rect:Rectangle, drawRect:Rectangle} { private static function buildPartGeometry(part:Part):{rect:Rectangle, drawRect:Rectangle} {
var source = new Rectangle(part.rect.width * part.gridX, part.rect.height * part.gridY, part.rect.width, part.rect.height); var source = new Rectangle()
var rect = source.clone(); .setX(part.rect.width * part.point.x)
.setY(part.rect.height * part.point.y)
.setWidth(part.rect.width)
.setHeight(part.rect.height);
var rect = cast(source, RectangleExt).clone();
var offset = rect.width / 4 + rect.width * 0.05; var offset = rect.width / 4 + rect.width * 0.05;
rect.x -= offset; rect.x -= offset;
rect.y -= offset; rect.y -= offset;
rect.width += offset * 2; rect.width += offset * 2;
rect.height += offset * 2; rect.height += offset * 2;
var drawRect = source.clone(); var drawRect = cast(source, RectangleExt).clone();
drawRect.x = offset; drawRect.x = offset;
drawRect.y = offset ; drawRect.y = offset ;
return {rect:rect, drawRect:drawRect}; return {rect:rect, drawRect:drawRect};
@@ -113,10 +118,10 @@ class RenderUtil {
var s = Math.min(1, Math.min(target.width / source.width, target.height / source.height)); var s = Math.min(1, Math.min(target.width / source.width, target.height / source.height));
var width = source.width * s; var width = source.width * s;
var height = source.height * s; var height = source.height * s;
return new Rectangle((target.width - width) / 2, (target.height - height) / 2, width, height); return new Rectangle()
} .setX((target.width - width) / 2)
.setY((target.height - height) / 2)
public static function convertPoint(point:flash.geom.Point):Point { .setWidth(width)
return new Point(point.x, point.y); .setHeight(height);
} }
} }

View File

@@ -1,8 +1,10 @@
package ru.m.puzzlez.render.part; package ru.m.puzzlez.render.part;
import hw.geom.Rectangle; import ru.m.puzzlez.wrap.RectangleExt;
import ru.m.puzzlez.proto.core.Rectangle;
import ru.m.draw.DrawPath; import ru.m.draw.DrawPath;
import ru.m.puzzlez.core.Part; import ru.m.puzzlez.proto.game.PartBound;
import ru.m.puzzlez.proto.game.PartBounds;
import ru.m.puzzlez.render.part.IPartBuilder; import ru.m.puzzlez.render.part.IPartBuilder;
class BasePartBuilder implements IPartBuilder { class BasePartBuilder implements IPartBuilder {
@@ -17,7 +19,7 @@ class BasePartBuilder implements IPartBuilder {
private function createBound(path:DrawPath, fromX:Float, fromY:Float, toX:Float, toY:Float, bound:PartBound):Void { private function createBound(path:DrawPath, fromX:Float, fromY:Float, toX:Float, toY:Float, bound:PartBound):Void {
} }
public function build(rect:Rectangle, bounds:PartBounds):DrawPath { public function build(rect:RectangleExt, bounds:PartBounds):DrawPath {
var path = new DrawPath(); var path = new DrawPath();
createAngle(path, rect.left, rect.top, true); createAngle(path, rect.left, rect.top, true);
createBound(path, rect.left, rect.top, rect.right, rect.top, bounds.top); createBound(path, rect.left, rect.top, rect.right, rect.top, bounds.top);

View File

@@ -1,8 +1,8 @@
package ru.m.puzzlez.render.part; package ru.m.puzzlez.render.part;
import ru.m.draw.DrawPath; import ru.m.draw.DrawPath;
import ru.m.puzzlez.core.BoundType; import ru.m.puzzlez.proto.game.BoundType;
import ru.m.puzzlez.core.Part; import ru.m.puzzlez.proto.game.PartBound;
class ClassicPartBuilder extends BasePartBuilder { class ClassicPartBuilder extends BasePartBuilder {
@@ -13,18 +13,17 @@ class ClassicPartBuilder extends BasePartBuilder {
var spikeWidth = boundLength * 0.2; var spikeWidth = boundLength * 0.2;
var spikeOffset = (boundLength - spikeWidth) / 2; var spikeOffset = (boundLength - spikeWidth) / 2;
var spikeDepth = switch bound.spike { var spikeDepth = switch bound.spike {
case NONE: 0; case BoundType.IN: spikeWidth;
case IN: spikeWidth; case BoundType.OUT: -spikeWidth;
case OUT: -spikeWidth; case _: 0;
}; };
var spikeSideOffset = switch bound.side { var spikeSideOffset = switch bound.side {
case NONE: 0; case BoundType.IN: boundLength * -0.04;
case IN: boundLength * -0.04; case BoundType.OUT: boundLength * 0.04;
case OUT: boundLength * 0.04; case _: 0;
} }
switch bound.spike { switch bound.spike {
case NONE: case BoundType.IN | BoundType.OUT:
case IN | OUT:
path.commands.push(LINE_TO( path.commands.push(LINE_TO(
fromX + spikeOffset * dx + spikeSideOffset * dy, fromX + spikeOffset * dx + spikeSideOffset * dy,
fromY + spikeOffset * dy + spikeSideOffset * dx fromY + spikeOffset * dy + spikeSideOffset * dx
@@ -41,6 +40,7 @@ class ClassicPartBuilder extends BasePartBuilder {
fromX + (spikeOffset + spikeWidth) * dx + spikeSideOffset * dy, fromX + (spikeOffset + spikeWidth) * dx + spikeSideOffset * dy,
fromY + (spikeOffset + spikeWidth) * dy + spikeSideOffset * dx fromY + (spikeOffset + spikeWidth) * dy + spikeSideOffset * dx
)); ));
case _:
} }
} }
} }

View File

@@ -1,9 +1,9 @@
package ru.m.puzzlez.render.part; package ru.m.puzzlez.render.part;
import hw.geom.Rectangle;
import ru.m.draw.DrawPath; import ru.m.draw.DrawPath;
import ru.m.puzzlez.core.Part; import ru.m.puzzlez.proto.game.PartBounds;
import ru.m.puzzlez.wrap.RectangleExt;
@:provide(ClassicPartBuilder) interface IPartBuilder { @:provide(ClassicPartBuilder) interface IPartBuilder {
public function build(rect:Rectangle, bound:PartBounds):DrawPath; public function build(rect:RectangleExt, bound:PartBounds):DrawPath;
} }

View File

@@ -1,8 +1,8 @@
package ru.m.puzzlez.render.part; package ru.m.puzzlez.render.part;
import ru.m.draw.DrawPath; import ru.m.draw.DrawPath;
import ru.m.puzzlez.core.BoundType; import ru.m.puzzlez.proto.game.BoundType;
import ru.m.puzzlez.core.Part; import ru.m.puzzlez.proto.game.PartBound;
class SquarePartBuilder extends BasePartBuilder { class SquarePartBuilder extends BasePartBuilder {
@@ -13,13 +13,13 @@ class SquarePartBuilder extends BasePartBuilder {
var spikeWidth = boundLength * 0.2; var spikeWidth = boundLength * 0.2;
var spikeOffset = (boundLength - spikeWidth) / 2; var spikeOffset = (boundLength - spikeWidth) / 2;
var spikeDepth = switch bound.spike { var spikeDepth = switch bound.spike {
case NONE: 0; case BoundType.NONE: 0;
case IN: spikeWidth; case BoundType.IN: spikeWidth;
case OUT: -spikeWidth; case BoundType.OUT: -spikeWidth;
} }
switch bound.spike { switch bound.spike {
case NONE: case BoundType.NONE:
case IN | OUT: case BoundType.IN | BoundType.OUT:
path.commands.push(LINE_TO( path.commands.push(LINE_TO(
fromX + spikeOffset * dx, fromX + spikeOffset * dx,
fromY + spikeOffset * dy fromY + spikeOffset * dy

View File

@@ -1,27 +1,32 @@
package ru.m.puzzlez.storage; package ru.m.puzzlez.storage;
import ru.m.data.DataStorage; import ru.m.data.DataStorage;
import ru.m.puzzlez.core.GameState;
import ru.m.puzzlez.core.Id; import ru.m.puzzlez.core.Id;
import ru.m.puzzlez.core.ImageListSource; import ru.m.puzzlez.core.ImageListSource;
import ru.m.puzzlez.proto.game.GameState;
@:provide class GameStorage extends DataStorage<ImageId, GameState> { @:provide class GameStorage extends DataStorage<ImageId, GameState> {
inline private static var NAME = "game"; inline private static var NAME = "game";
inline private static var VERSION = 4; inline private static var VERSION = 6;
public function new() { public function new() {
super( super(
'${NAME}/${VERSION}', '${NAME}/${VERSION}',
new MetaBuilder<GameState>(item -> ["id" => item.preset.imageId, "status" => item.status, "date" => Date.now().toString()]), new MetaBuilder<GameState>(item -> [
new Converter<ImageId, String>(id -> id.toString(), data -> ImageId.fromString(data)) "id" => item.preset.imageId,
"status" => Std.string(item.status),
"date" => Date.now().toString()
]),
new Converter<ImageId, String>(id -> id.toString(), data -> ImageId.fromString(data)),
new ProtoConverter(GameState)
); );
} }
public function statusSource(status:GameStatus):ImageListSource { public function statusSource(status:Int):ImageListSource {
return { return {
title: status, title: Std.string(status), // # ToDo:
source: this, source: this,
filter: ["status" => status], filter: ["status" => Std.string(status)],
} }
} }
} }

View File

@@ -1,15 +1,16 @@
package ru.m.puzzlez.view; package ru.m.puzzlez.view;
import ru.m.puzzlez.proto.game.GameStatus;
import haxe.Timer; import haxe.Timer;
import hw.view.frame.FrameSwitcher; import hw.view.frame.FrameSwitcher;
import hw.view.frame.FrameView; import hw.view.frame.FrameView;
import hw.view.popup.ConfirmView; import hw.view.popup.ConfirmView;
import promhx.Promise; import promhx.Promise;
import ru.m.puzzlez.core.Game; import ru.m.puzzlez.core.Game;
import ru.m.puzzlez.core.GameEvent;
import ru.m.puzzlez.core.GameState;
import ru.m.puzzlez.core.IGame; import ru.m.puzzlez.core.IGame;
import ru.m.puzzlez.net.NetworkGame; import ru.m.puzzlez.net.NetworkGame;
import ru.m.puzzlez.proto.event.GameEvent;
import ru.m.puzzlez.proto.game.GameState;
import ru.m.puzzlez.render.IRender; import ru.m.puzzlez.render.IRender;
import ru.m.puzzlez.storage.GameStorage; import ru.m.puzzlez.storage.GameStorage;
import ru.m.puzzlez.storage.SettingsStorage; import ru.m.puzzlez.storage.SettingsStorage;
@@ -73,10 +74,8 @@ import ru.m.puzzlez.view.popup.PreviewPopup;
} }
private function onGameEvent(event:GameEvent):Void { private function onGameEvent(event:GameEvent):Void {
switch event { if (event.hasStart() || event.hasAction()) {
case START(_) | ACTION(PART_PUT(_, _)): toSave();
toSave();
case _:
} }
} }
@@ -94,7 +93,7 @@ import ru.m.puzzlez.view.popup.PreviewPopup;
} }
private function back():Void { private function back():Void {
(game.state.status == COMPLETE ? Promise.promise(true) : ConfirmView.confirm("Exit?")) (game.state.status == GameStatus.COMPLETE ? Promise.promise(true) : ConfirmView.confirm("Exit?"))
.then(result -> { .then(result -> {
if (result) { if (result) {
switcher.change(ImageListFrame.ID, storage.statusSource(game.state.status)); switcher.change(ImageListFrame.ID, storage.statusSource(game.state.status));

View File

@@ -1,5 +1,6 @@
package ru.m.puzzlez.view; package ru.m.puzzlez.view;
import ru.m.puzzlez.proto.game.GameStatus;
import ru.m.puzzlez.net.Network; import ru.m.puzzlez.net.Network;
import hw.view.data.DataView; import hw.view.data.DataView;
import hw.view.form.ButtonView; import hw.view.form.ButtonView;
@@ -7,7 +8,6 @@ import hw.view.frame.FrameSwitcher;
import hw.view.frame.FrameView; import hw.view.frame.FrameView;
import ru.m.data.IDataSource; import ru.m.data.IDataSource;
import ru.m.pixabay.PixabayApi; import ru.m.pixabay.PixabayApi;
import ru.m.puzzlez.core.GameState.GameStatus;
import ru.m.puzzlez.core.ImageListSource; import ru.m.puzzlez.core.ImageListSource;
import ru.m.puzzlez.source.AssetSource; import ru.m.puzzlez.source.AssetSource;
import ru.m.puzzlez.source.FileSource; import ru.m.puzzlez.source.FileSource;
@@ -43,13 +43,13 @@ import ru.m.update.Updater;
} }
private function refresh():Void { private function refresh():Void {
var startedRequest:Page = {index: 0, count: 0, filter: ["status" => STARTED]}; var startedRequest:Page = {index: 0, count: 0, filter: ["status" => Std.string(GameStatus.STARTED)]};
gameStorage.getIndexPage(startedRequest).then(page -> { gameStorage.getIndexPage(startedRequest).then(page -> {
var total = page.total; var total = page.total;
loadButton.text = 'Resume (${total})'; loadButton.text = 'Resume (${total})';
loadButton.disabled = total == 0; loadButton.disabled = total == 0;
}); });
var completeRequest:Page = {index: 0, count: 0, filter: ["status" => COMPLETE]}; var completeRequest:Page = {index: 0, count: 0, filter: ["status" => Std.string(GameStatus.COMPLETE)]};
gameStorage.getIndexPage(completeRequest).then(page -> { gameStorage.getIndexPage(completeRequest).then(page -> {
var total = page.total; var total = page.total;
completeButton.text = 'Complete (${total})'; completeButton.text = 'Complete (${total})';
@@ -77,7 +77,7 @@ import ru.m.update.Updater;
switcher.change(ImageListFrame.ID, source); switcher.change(ImageListFrame.ID, source);
} }
private function openGames(status:GameStatus):Void { private function openGames(status:Int):Void {
switcher.change(ImageListFrame.ID, gameStorage.statusSource(status)); switcher.change(ImageListFrame.ID, gameStorage.statusSource(status));
} }

View File

@@ -35,11 +35,11 @@ views:
- id: load - id: load
$type: hw.view.form.ButtonView $type: hw.view.form.ButtonView
text: Load text: Load
+onPress: ~openGames('started') +onPress: ~openGames(1)
- id: complete - id: complete
$type: hw.view.form.ButtonView $type: hw.view.form.ButtonView
text: Complete text: Complete
+onPress: ~openGames('complete') +onPress: ~openGames(2)
- id: network - id: network
$type: hw.view.form.ButtonView $type: hw.view.form.ButtonView
text: Network text: Network

View File

@@ -1,10 +1,11 @@
package ru.m.puzzlez.view.common; package ru.m.puzzlez.view.common;
import ru.m.puzzlez.wrap.RectangleExt;
import ru.m.puzzlez.proto.game.GameState;
import flash.display.Graphics; import flash.display.Graphics;
import flash.display.BitmapData; import flash.display.BitmapData;
import flash.display.Shape; import flash.display.Shape;
import hw.view.group.GroupView; import hw.view.group.GroupView;
import ru.m.puzzlez.core.GameState;
import ru.m.puzzlez.render.part.IPartBuilder; import ru.m.puzzlez.render.part.IPartBuilder;
import ru.m.puzzlez.render.RenderUtil; import ru.m.puzzlez.render.RenderUtil;
import ru.m.puzzlez.storage.ImageStorage; import ru.m.puzzlez.storage.ImageStorage;
@@ -57,8 +58,8 @@ class PresetView extends GroupView {
return; return;
} }
var preset = state.preset; var preset = state.preset;
var partWidth = preset.imageRect.width / preset.grid.width; var partWidth = preset.imageRect.width / preset.grid.x;
var partHeight = preset.imageRect.height / preset.grid.height; var partHeight = preset.imageRect.height / preset.grid.y;
var graphics:Graphics = table.graphics; var graphics:Graphics = table.graphics;
graphics.clear(); graphics.clear();
graphics.beginBitmapFill(image, null, false, true); graphics.beginBitmapFill(image, null, false, true);
@@ -66,9 +67,9 @@ class PresetView extends GroupView {
graphics.endFill(); graphics.endFill();
for (part in state.parts) { for (part in state.parts) {
var rect = part.rect.clone(); var rect = cast(part.rect, RectangleExt).clone();
rect.x = part.gridX * part.rect.width; rect.x = part.point.x * part.rect.width;
rect.y = part.gridY * part.rect.height; rect.y = part.point.y * part.rect.height;
var path = builder.build(rect, part.bounds); var path = builder.build(rect, part.bounds);
for (value in RenderUtil.borderSettings) { for (value in RenderUtil.borderSettings) {
graphics.lineStyle(1, value.color, value.opacity); graphics.lineStyle(1, value.color, value.opacity);

View File

@@ -1,9 +1,9 @@
package ru.m.puzzlez.view.popup; package ru.m.puzzlez.view.popup;
import ru.m.puzzlez.proto.game.GameState;
import flash.events.MouseEvent; import flash.events.MouseEvent;
import hw.view.popup.PopupView; import hw.view.popup.PopupView;
import promhx.Promise; import promhx.Promise;
import ru.m.puzzlez.core.GameState;
import ru.m.puzzlez.view.common.PresetView; import ru.m.puzzlez.view.common.PresetView;
@:singleton @:template class PreviewPopup extends PopupView<Dynamic> { @:singleton @:template class PreviewPopup extends PopupView<Dynamic> {

View File

@@ -1,7 +0,0 @@
package ru.m.puzzlez.core;
enum BoundType {
NONE;
OUT;
IN;
}

View File

@@ -0,0 +1,36 @@
package ru.m.puzzlez.core;
import ru.m.puzzlez.proto.event.GameComplete;
import ru.m.puzzlez.proto.event.GameChange;
import ru.m.puzzlez.proto.game.Part;
import ru.m.puzzlez.proto.event.GameAction;
import ru.m.puzzlez.proto.event.GameStart;
import ru.m.puzzlez.proto.event.GameEvent;
import ru.m.puzzlez.proto.game.GameState;
class EventUtil {
public static function start(state:GameState, resume: Bool):GameEvent {
return new GameEvent().setStart(new GameStart()
.setState(state)
.setResume(resume)
);
}
public static function complete():GameEvent {
return new GameEvent().setComplete(new GameComplete());
}
public static function action(action:GameAction):GameEvent {
return new GameEvent().setAction(action);
}
public static function change(part:Part):GameEvent {
return new GameEvent().setChange(new GameChange()
.setPartId(part.id)
.setLocation(part.location)
.setPosition(part.position)
.setPlayerId(part.playerId)
);
}
}

View File

@@ -1,10 +1,15 @@
package ru.m.puzzlez.core; package ru.m.puzzlez.core;
import hw.geom.Point; import ru.m.puzzlez.proto.core.Point;
import hw.signal.Signal; import hw.signal.Signal;
import ru.m.puzzlez.core.GameEvent; import ru.m.puzzlez.proto.event.gameaction.Action;
import ru.m.puzzlez.core.GameState; import ru.m.puzzlez.proto.event.GameAction;
import ru.m.puzzlez.core.PartLocation; import ru.m.puzzlez.proto.event.GameEvent;
import ru.m.puzzlez.proto.game.GameState;
import ru.m.puzzlez.proto.game.GameStatus;
import ru.m.puzzlez.proto.game.Part;
import ru.m.puzzlez.proto.game.PartLocation;
import ru.m.puzzlez.wrap.PointExt;
class Game implements IGame { class Game implements IGame {
public var state(default, null):GameState; public var state(default, null):GameState;
@@ -23,42 +28,42 @@ class Game implements IGame {
} }
public function action(action:GameAction):Void { public function action(action:GameAction):Void {
events.emit(ACTION(action)); events.emit(EventUtil.action(action));
} }
public function start():Void { public function start():Void {
switch state.status { switch state.status {
case READY: case GameStatus.READY:
shuffle(); shuffle();
state.status = STARTED; state.status = GameStatus.STARTED;
events.emit(START(state, false)); events.emit(EventUtil.start(state, false));
case _: case _:
events.emit(START(state, true)); events.emit(EventUtil.start(state, true));
} }
} }
private function shuffle():Void { private function shuffle():Void {
for (part in state.parts) { for (part in state.parts) {
switch part.location { switch part.location {
case TABLE(_): case PartLocation.TABLE:
var bound = part.rect.width * 0.25; var bound = part.rect.width * 0.25;
var x = bound + Math.random() * (state.preset.tableRect.width - part.rect.width - bound * 2); var x = bound + Math.random() * (state.preset.tableRect.width - part.rect.width - bound * 2);
var y = bound + Math.random() * (state.preset.tableRect.height - part.rect.height - bound * 2); var y = bound + Math.random() * (state.preset.tableRect.height - part.rect.height - bound * 2);
part.location = TABLE(new Point(x, y)); part.position = new PointExt(x, y);
case _: case _:
} }
} }
} }
private static function distance(a:Point, b:Point):Float { private static function distance(a:PointExt, b:PointExt):Float {
var diff = a.subtract(b); var diff:Point = a.subtract(b);
return Math.abs(diff.x) + Math.abs(diff.y); return Math.abs(diff.x) + Math.abs(diff.y);
} }
private function checkIsComplete():Bool { private function checkIsComplete():Bool {
for (part in partsById) { for (part in partsById) {
switch part.location { switch part.location {
case IMAGE: case PartLocation.IMAGE:
case _: return false; case _: return false;
} }
} }
@@ -66,40 +71,42 @@ class Game implements IGame {
} }
private function onGameEvent(event:GameEvent):Void { private function onGameEvent(event:GameEvent):Void {
switch event { if (event.hasAction()) {
case ACTION(PART_TAKE(playerId, partId)): var part:Part = partsById[event.action.partId];
var part = partsById[partId]; switch event.action.action {
switch part.location { case Action.TAKE:
case TABLE(point): switch part.location {
part.location = HAND(playerId, point); case PartLocation.TABLE:
events.emit(CHANGE(PART_UPDATE(partId, part.location))); part.location = PartLocation.HAND;
case _: part.playerId = event.action.playerId;
} events.emit(EventUtil.change(part));
case ACTION(PART_MOVE(playerId, partId, point)): case _:
var part = partsById[partId];
switch part.location {
case HAND(currentPlayerId, _) if (currentPlayerId == playerId):
part.location = HAND(playerId, point);
events.emit(CHANGE(PART_UPDATE(partId, part.location)));
case _:
}
case ACTION(PART_PUT(playerId, partId, point)):
var part:Part = partsById[partId];
var target:Point = state.preset.imageRect.position.clone();
target = target.add(new Point(part.gridX * part.rect.width, part.gridY * part.rect.height));
var d = distance(target, point);
if (d < 70) {
part.location = IMAGE;
events.emit(CHANGE(PART_UPDATE(partId, part.location)));
if (checkIsComplete()) {
state.status = COMPLETE;
events.emit(COMPLETE);
} }
} else { case Action.MOVE:
part.location = TABLE(point); switch part.location {
events.emit(CHANGE(PART_UPDATE(partId, part.location))); case PartLocation.HAND if (event.action.playerId == part.playerId):
} part.position = event.action.position;
case _: events.emit(EventUtil.change(part));
case _:
}
case Action.PUT:
part.playerId = null;
var target = new PointExt(state.preset.imageRect.x, state.preset.imageRect.y)
.add(new PointExt(part.point.x * part.rect.width, part.point.y * part.rect.height));
var d = distance(target, event.action.position);
if (d < 70) {
part.location = PartLocation.IMAGE;
events.emit(EventUtil.change(part));
if (checkIsComplete()) {
state.status = GameStatus.COMPLETE;
events.emit(EventUtil.complete());
}
} else {
part.location = PartLocation.TABLE;
part.position = event.action.position;
events.emit(EventUtil.change(part));
}
}
} }
} }

View File

@@ -1,21 +0,0 @@
package ru.m.puzzlez.core;
import ru.m.puzzlez.core.Id;
import hw.geom.Point;
enum GameAction {
PART_TAKE(playerId:PlayerId, partId:PartId);
PART_MOVE(playerId:PlayerId, partId:PartId, point:Point);
PART_PUT(playerId:PlayerId, partId:PartId, point:Point);
}
enum GameChange {
PART_UPDATE(id:Int, location:PartLocation);
}
enum GameEvent {
START(state:GameState, resume:Bool);
ACTION(action:GameAction);
CHANGE(change:GameChange);
COMPLETE;
}

View File

@@ -1,11 +0,0 @@
package ru.m.puzzlez.core;
import hw.geom.Rectangle;
import ru.m.puzzlez.core.Id;
typedef GamePreset = {
var imageId:ImageId;
var grid:Grid;
var tableRect:Rectangle;
var imageRect:Rectangle;
}

View File

@@ -1,15 +0,0 @@
package ru.m.puzzlez.core;
enum abstract GameStatus(String) from String to String {
var READY = "ready";
var STARTED = "started";
var COMPLETE = "complete";
}
typedef GameState = {
var id:String;
var status:GameStatus;
var preset:GamePreset;
var parts:Array<Part>;
@:optional var online:Bool;
}

View File

@@ -1,65 +1,64 @@
package ru.m.puzzlez.core; package ru.m.puzzlez.core;
import hw.geom.Point;
import hw.geom.Rectangle;
import ru.m.puzzlez.core.BoundType;
import ru.m.puzzlez.core.GameState;
import ru.m.puzzlez.core.Id; import ru.m.puzzlez.core.Id;
import ru.m.puzzlez.core.Part;
import ru.m.puzzlez.core.PartLocation;
import ru.m.puzzlez.core.Side; import ru.m.puzzlez.core.Side;
import ru.m.puzzlez.proto.core.IntPoint;
import ru.m.puzzlez.proto.core.Point;
import ru.m.puzzlez.proto.core.Rectangle;
import ru.m.puzzlez.proto.game.BoundType;
import ru.m.puzzlez.proto.game.GamePreset;
import ru.m.puzzlez.proto.game.GameState;
import ru.m.puzzlez.proto.game.GameStatus;
import ru.m.puzzlez.proto.game.Part;
import ru.m.puzzlez.proto.game.PartBound;
import ru.m.puzzlez.proto.game.PartBounds;
import ru.m.puzzlez.proto.game.PartLocation;
class BoundsMap { class BoundsMap {
private var grid:Grid; private var grid:IntPoint;
private var bounds:Array<PartBound>; private var bounds:Array<PartBound>;
public function new(grid:Grid) { public function new(grid:IntPoint) {
this.grid = grid; this.grid = grid;
bounds = []; bounds = [];
for (x in 0...grid.width + 1) { for (x in 0...grid.x + 1) {
for (y in 0...grid.height + 1) { for (y in 0...grid.y + 1) {
bounds.push(buildPartBound()); bounds.push(buildPartBound());
bounds.push(buildPartBound()); bounds.push(buildPartBound());
} }
} }
} }
public function buildBound():BoundType { public function buildBound():Int {
return Math.random() > 0.5 ? BoundType.OUT : BoundType.IN; return Math.random() > 0.5 ? BoundType.OUT : BoundType.IN;
} }
public function buildPartBound():PartBound { public function buildPartBound():PartBound {
return { return new PartBound().setSpike(buildBound()).setSide(buildBound());
spike: buildBound(),
side: buildBound(),
}
} }
public function buildNonePartBound():PartBound { public function buildNonePartBound():PartBound {
return { return new PartBound().setSpike(BoundType.NONE).setSide(BoundType.NONE);
spike: NONE,
side: NONE,
}
} }
public function revert(type:BoundType):BoundType { public function revert(type:Int):Int {
return switch type { return switch type {
case IN: OUT; case BoundType.IN: BoundType.OUT;
case OUT: IN; case BoundType.OUT: BoundType.IN;
case NONE: NONE; case _: BoundType.NONE;
} }
} }
public function getBound(x:Int, y:Int, side:Side):PartBound { public function getBound(x:Int, y:Int, side:Side):PartBound {
var index = switch side { var index = switch side {
case TOP: x + (grid.width + 1) * (y * 2); case TOP: x + (grid.x + 1) * (y * 2);
case LEFT: x + (grid.width + 1) * (y + 1); case LEFT: x + (grid.x + 1) * (y + 1);
case RIGHT: x + (grid.width + 1) * (y + 1) + 1; case RIGHT: x + (grid.x + 1) * (y + 1) + 1;
case BOTTOM: x + (grid.width + 1) * (y * 2) + (grid.width + 1) * 2; case BOTTOM: x + (grid.x + 1) * (y * 2) + (grid.x + 1) * 2;
} }
return switch side { return switch side {
case TOP | LEFT: {spike: revert(bounds[index].spike), side: revert(bounds[index].side)}; case TOP | LEFT: new PartBound().setSpike(revert(bounds[index].spike)).setSide(revert(bounds[index].side));
case _: bounds[index]; case _: bounds[index];
} }
} }
@@ -80,67 +79,72 @@ class GameUtil {
var offsetY = 200; var offsetY = 200;
var imageSize = 1024; var imageSize = 1024;
var s = width / height; var s = width / height;
var imageRect = new Rectangle(offsetX, offsetY, imageSize, imageSize / s); var imageRect = new Rectangle()
var tableRect = new Rectangle(0, 0, imageRect.width + offsetX * 2, imageRect.height + offsetY * 2); .setX(offsetX)
return { .setY(offsetY)
imageId: imageId, .setWidth(imageSize)
grid: {width: width, height: height}, .setHeight(imageSize / s);
tableRect: tableRect, var tableRect = new Rectangle()
imageRect: imageRect, .setX(0)
} .setY(0)
.setWidth(imageRect.width + offsetX * 2)
.setHeight(imageRect.height + offsetY * 2);
return new GamePreset()
.setImageId(imageId)
.setGrid(new IntPoint().setX(width).setY(height))
.setTableRect(tableRect)
.setImageRect(imageRect);
} }
public static function buildState(preset:GamePreset):GameState { public static function buildState(preset:GamePreset):GameState {
var parts:Array<Part> = []; var parts:Array<Part> = [];
var partWidth = preset.imageRect.width / preset.grid.width; var partWidth = preset.imageRect.width / preset.grid.x;
var partHeight = preset.imageRect.height / preset.grid.height; var partHeight = preset.imageRect.height / preset.grid.y;
var position = preset.imageRect.position; var position = new Point().setX(preset.imageRect.x).setY(preset.imageRect.y);
var boundsMap = new BoundsMap(preset.grid); var boundsMap = new BoundsMap(preset.grid);
for (y in 0...preset.grid.height) { for (y in 0...preset.grid.y) {
for (x in 0...preset.grid.width) { for (x in 0...preset.grid.x) {
var bounds:PartBounds = { var bounds:PartBounds = new PartBounds()
left: boundsMap.getBound(x, y, LEFT), .setLeft(boundsMap.getBound(x, y, LEFT))
right: boundsMap.getBound(x, y, RIGHT), .setRight(boundsMap.getBound(x, y, RIGHT))
top: boundsMap.getBound(x, y, TOP), .setTop(boundsMap.getBound(x, y, TOP))
bottom: boundsMap.getBound(x, y, BOTTOM), .setBottom(boundsMap.getBound(x, y, BOTTOM));
}
if (x == 0) { if (x == 0) {
bounds.left = boundsMap.buildNonePartBound(); bounds.left = boundsMap.buildNonePartBound();
} }
if (y == 0) { if (y == 0) {
bounds.top = boundsMap.buildNonePartBound(); bounds.top = boundsMap.buildNonePartBound();
} }
if (x == preset.grid.width - 1) { if (x == preset.grid.x - 1) {
bounds.right = boundsMap.buildNonePartBound(); bounds.right = boundsMap.buildNonePartBound();
} }
if (y == preset.grid.height - 1) { if (y == preset.grid.y - 1) {
bounds.bottom = boundsMap.buildNonePartBound(); bounds.bottom = boundsMap.buildNonePartBound();
} }
var id = (x << 16) + y; var id = (x << 16) + y;
var position = new Point(position.x + x * partWidth, position.y + y * partHeight); var position = new Point().setX(position.x + x * partWidth).setY(position.y + y * partHeight);
parts.push({ parts.push(new Part()
id: id, .setId(id)
gridX: x, .setPoint(new IntPoint().setX(x).setY(y))
gridY: y, .setLocation(PartLocation.TABLE)
location: TABLE(position), .setPosition(position)
bounds: bounds, .setBounds(bounds)
rect: new Rectangle(0, 0, partWidth, partHeight), .setRect(new Rectangle().setX(0).setY(0).setWidth(partWidth).setHeight(partHeight))
}); );
} }
} }
return { return new GameState()
id: IdUtil.generate(), .setId(IdUtil.generate())
status: READY, .setStatus(GameStatus.READY)
preset: preset, .setPreset(preset)
parts: parts, .setParts(parts);
}
} }
public static function calcProgress(state:GameState):{complete:Int, total:Int} { public static function calcProgress(state:GameState):{complete:Int, total:Int} {
var complete = 0; var complete = 0;
for (part in state.parts) { for (part in state.parts) {
switch part.location { switch part.location {
case IMAGE: complete++; case PartLocation.IMAGE: complete++;
case _: case _:
} }
} }

View File

@@ -1,6 +0,0 @@
package ru.m.puzzlez.core;
typedef Grid = {
var width:Int;
var height:Int;
}

View File

@@ -1,7 +1,9 @@
package ru.m.puzzlez.core; package ru.m.puzzlez.core;
import ru.m.puzzlez.core.GameEvent.GameAction;
import hw.signal.Signal; import hw.signal.Signal;
import ru.m.puzzlez.proto.event.GameAction;
import ru.m.puzzlez.proto.event.GameEvent;
import ru.m.puzzlez.proto.game.GameState;
interface IGame { interface IGame {
public var state(default, null):GameState; public var state(default, null):GameState;

View File

@@ -1,24 +0,0 @@
package ru.m.puzzlez.core;
import hw.geom.Rectangle;
typedef PartBound = {
var spike:BoundType;
var side:BoundType;
}
typedef PartBounds = {
var left:PartBound;
var right:PartBound;
var top:PartBound;
var bottom:PartBound;
}
typedef Part = {
var id:Int;
var gridX:Int;
var gridY:Int;
var location:PartLocation;
var bounds:PartBounds;
var rect:Rectangle;
}

View File

@@ -1,10 +0,0 @@
package ru.m.puzzlez.core;
import hw.geom.Point;
import ru.m.puzzlez.core.Id;
enum PartLocation {
HAND(playerId:PlayerId, point:Point);
TABLE(point:Point);
IMAGE;
}

View File

@@ -0,0 +1,41 @@
package ru.m.puzzlez.wrap;
import ru.m.puzzlez.proto.core.Point;
#if openfl
import flash.geom.Point as FlashPoint;
#end
abstract PointExt(Point) from Point to Point {
public function new(x: Float = 0, y: Float = 0) {
this = new Point()
.setX(x)
.setY(y);
}
public function clone():PointExt {
return new Point()
.setX(this.x)
.setY(this.y);
}
public function add(point:Point):PointExt {
return new Point()
.setX(this.x + point.x)
.setY(this.y + point.y);
}
public function subtract(point:Point):PointExt {
return new Point()
.setX(this.x - point.x)
.setY(this.y - point.y);
}
#if openfl
@:from public static function fromFlashPoint(point:FlashPoint):PointExt {
return new Point()
.setX(point.x)
.setY(point.y);
}
#end
}

View File

@@ -0,0 +1,50 @@
package ru.m.puzzlez.wrap;
import ru.m.puzzlez.proto.core.Point;
import ru.m.puzzlez.proto.core.Rectangle;
abstract RectangleExt(Rectangle) from Rectangle to Rectangle {
public var left(get, never):Float;
public var right(get, never):Float;
public var top(get, never):Float;
public var bottom(get, never):Float;
public var size(get, never):Point;
public function new(x:Float = 0, y:Float = 0, width:Float = 0, height:Float = 0) {
this = new Rectangle()
.setX(x)
.setY(y)
.setWidth(width)
.setHeight(height);
}
private function get_left():Float {
return this.x;
}
private function get_right():Float {
return this.x + this.width;
}
private function get_top():Float {
return this.y;
}
private function get_bottom():Float {
return this.y + this.height;
}
private function get_size():Point {
return new Point()
.setX(this.width)
.setY(this.height);
}
public function clone():Rectangle {
return new Rectangle()
.setX(this.x)
.setY(this.y)
.setWidth(this.width)
.setHeight(this.height);
}
}

View File

@@ -2,12 +2,24 @@ syntax = "proto3";
package ru.m.puzzlez.proto.core; package ru.m.puzzlez.proto.core;
message PointProto { message Point {
float x = 1; float x = 1;
float y = 2; float y = 2;
} }
message UserProto { message IntPoint {
int32 x = 1;
int32 y = 2;
}
message Rectangle {
float x = 1;
float y = 2;
float width = 3;
float height = 4;
}
message User {
string uuid = 1; string uuid = 1;
string name = 2; string name = 2;
} }

View File

@@ -5,16 +5,16 @@ import "game.proto";
package ru.m.puzzlez.proto.event; package ru.m.puzzlez.proto.event;
message GameStartProto { message GameStart {
ru.m.puzzlez.proto.game.GameStateProto state = 1; ru.m.puzzlez.proto.game.GameState state = 1;
bool resume = 2; bool resume = 2;
} }
message GameCompleteProto { message GameComplete {
} }
message GameActionProto { message GameAction {
string playerId = 1; string playerId = 1;
int32 partId = 2; int32 partId = 2;
enum Action { enum Action {
@@ -23,19 +23,22 @@ message GameActionProto {
PUT = 2; PUT = 2;
} }
Action action = 3; Action action = 3;
ru.m.puzzlez.proto.core.PointProto point = 4; ru.m.puzzlez.proto.core.Point position = 4;
} }
message GameChangeProto { message GameChange {
int32 partId = 1; int32 partId = 1;
ru.m.puzzlez.proto.core.Point position = 2;
ru.m.puzzlez.proto.game.PartLocation location = 3;
string playerId = 4;
} }
message GameEventProto { message GameEvent {
int32 time = 1; int32 time = 1;
oneof event { oneof event {
GameStartProto start = 10; GameStart start = 10;
GameCompleteProto complete = 11; GameComplete complete = 11;
GameActionProto action = 12; GameAction action = 12;
GameChangeProto change = 13; GameChange change = 13;
} }
} }

View File

@@ -4,27 +4,62 @@ import "core.proto";
package ru.m.puzzlez.proto.game; package ru.m.puzzlez.proto.game;
message PartProto { enum BoundType {
NONE = 0;
OUT = 1;
IN = 2;
} }
message GridProto { message PartBound {
int32 width = 1; BoundType spike = 1;
int32 height = 2; BoundType side = 2;
} }
message GamePresetProto { message PartBounds {
PartBound left = 1;
PartBound right = 2;
PartBound top = 3;
PartBound bottom = 4;
}
enum PartLocation {
TABLE = 0;
HAND = 1;
IMAGE = 2;
}
message Part {
int32 id = 1;
ru.m.puzzlez.proto.core.IntPoint point = 2;
PartBounds bounds = 3;
ru.m.puzzlez.proto.core.Point position = 4;
PartLocation location = 5;
ru.m.puzzlez.proto.core.Rectangle rect = 6;
string playerId = 7;
}
message GamePreset {
string imageId = 1; string imageId = 1;
GridProto grid = 2; ru.m.puzzlez.proto.core.IntPoint grid = 2;
ru.m.puzzlez.proto.core.Rectangle tableRect = 3;
ru.m.puzzlez.proto.core.Rectangle imageRect = 4;
} }
message GameStateProto { enum GameStatus {
READY = 0;
STARTED = 1;
COMPLETE = 2;
}
message GameState {
string id = 1; string id = 1;
GamePresetProto preset = 2; GameStatus status = 2;
repeated PartProto parts = 3; GamePreset preset = 3;
repeated Part parts = 4;
bool online = 5;
} }
message GameProto { message GameItem {
GameStateProto state = 2; GameState state = 2;
repeated ru.m.puzzlez.proto.core.UserProto users = 3; repeated ru.m.puzzlez.proto.core.User users = 3;
} }

View File

@@ -12,11 +12,11 @@ message ErrorResponse {
} }
message LoginRequest { message LoginRequest {
ru.m.puzzlez.proto.core.UserProto user = 1; ru.m.puzzlez.proto.core.User user = 1;
} }
message LoginResponse { message LoginResponse {
ru.m.puzzlez.proto.core.UserProto user = 1; ru.m.puzzlez.proto.core.User user = 1;
} }
message LogoutRequest {} message LogoutRequest {}
@@ -34,7 +34,7 @@ message GameJoinRequest {
message GameLeaveRequest {} message GameLeaveRequest {}
message GameResponse { message GameResponse {
ru.m.puzzlez.proto.game.GameProto game = 1; ru.m.puzzlez.proto.game.GameItem game = 1;
} }
message GameListRequest { message GameListRequest {
@@ -42,15 +42,15 @@ message GameListRequest {
} }
message GameListResponse { message GameListResponse {
repeated ru.m.puzzlez.proto.game.GameProto games = 1; repeated ru.m.puzzlez.proto.game.GameItem games = 1;
} }
message GameActionRequest { message GameActionRequest {
repeated ru.m.puzzlez.proto.event.GameActionProto actions = 1; repeated ru.m.puzzlez.proto.event.GameAction actions = 1;
} }
message GameEventResponse { message GameEventResponse {
repeated ru.m.puzzlez.proto.event.GameEventProto events = 1; repeated ru.m.puzzlez.proto.event.GameEvent events = 1;
} }
message Request { message Request {