style: format code

This commit is contained in:
2024-02-12 00:59:07 +03:00
parent 2370c9c2b4
commit 05c736ddd1
84 changed files with 2868 additions and 2977 deletions

View File

@@ -4,13 +4,10 @@ root = true
[*] [*]
charset = utf-8 charset = utf-8
indent_style = space indent_style = space
indent_size = 4 indent_size = 2
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.yaml]
indent_size = 2
[*.md] [*.md]
max_line_length = off max_line_length = off
trim_trailing_whitespace = false trim_trailing_whitespace = false

32
.vscode/launch.json vendored
View File

@@ -1,32 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"args": [
"app:flash:test"
],
"name": "app:flash:test",
"program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
},
{
"args": [
"server:cpp:test"
],
"name": "server:cpp:test",
"program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
}
]
}

16
.vscode/settings.json vendored
View File

@@ -1,16 +0,0 @@
{
"haxe.executable": {
"path": "/home/shmyga/sdk/haxe/4.2.5/haxe",
"env": {
"HAXE_STD_PATH": "/home/shmyga/sdk/haxe/4.2.5/std",
},
},
"haxe.configurations": [
["build/app/flash/haxe/debug.hxml"],
],
"haxe.displayServer": {
"arguments": [
//"-v"
],
}
}

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
`.bashrc`
```bash
export NEKO_VERSION="2.2.0"
export HAXE_VERSION="4.2.5"
export NEKO_SDK="$HOME/sdk/neko/$NEKO_VERSION"
export HAXE_SDK="$HOME/sdk/haxe/$HAXE_VERSION"
alias haxe-activate=". $NEKO_SDK/activate && . $HAXE_SDK/activate"
```

View File

@@ -1,11 +1,11 @@
{ {
"SdkDir": "C:\\sdk", "SdkDir": "C:\\sdk",
"BuildDir": null, "BuildDir": null,
"PublishDir": "", "PublishDir": "",
"PublishUrl": "https://shmyga.ru/repo/puzzlez", "PublishUrl": "https://shmyga.ru/repo/puzzlez",
"Develop": false, "Develop": false,
"Key": { "Key": {
"store": "keystore.jks", "store": "keystore.jks",
"pass": null "pass": null
} }
} }

5
hxformat.json Normal file
View File

@@ -0,0 +1,5 @@
{
"indentation": {
"character": " "
}
}

View File

@@ -1,24 +1,25 @@
{ {
"name": "puzzlez", "name": "puzzlez",
"version": "0.5.0", "version": "0.5.0",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"dateformat": "^3.0.3", "dateformat": "^3.0.3",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"gulp": "^4.0.0", "gulp": "^4.0.0",
"gulp-add": "0.0.2", "gulp-add": "0.0.2",
"gulp-clean": "^0.4.0", "gulp-clean": "^0.4.0",
"gulp-cli": "^2.2.0", "gulp-cli": "^2.2.0",
"gulp-haxetool": "^0.1.9", "gulp-haxetool": "^0.1.9",
"yargs": "^13.2.4" "yargs": "^13.2.4"
}, },
"haxeDependencies": { "haxeDependencies": {
"haxework": "2.1.0", "haxework": "2.1.0",
"lime": "8.0.0", "lime": "8.0.0",
"openfl": "9.2.0", "openfl": "9.2.0",
"hxcpp": "4.2.1", "hxcpp": "4.2.1",
"svg": "1.1.3", "svg": "1.1.3",
"protohx": "0.4.6" "protohx": "0.4.6",
}, "formatter": "1.16.0"
"haxe": "4.2.5" },
"haxe": "4.2.5"
} }

46
puzzlez.code-workspace Normal file
View File

@@ -0,0 +1,46 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"haxe.executable": {
"path": "/home/shmyga/sdk/haxe/4.2.5/haxe",
"env": {
"HAXE_STD_PATH": "/home/shmyga/sdk/haxe/4.2.5/std"
}
},
"haxe.configurations": [["build/app/flash/haxe/debug.hxml"]],
"haxe.displayServer": {
"arguments": [
//"-v"
]
}
},
"extensions": {
"recommendations": ["nadako.vshaxe"]
},
"launch": {
"version": "0.2.0",
"configurations": [
{
"args": ["app:flash:test"],
"name": "app:flash:test",
"program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js",
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "node"
},
{
"args": ["server:cpp:test"],
"name": "server:cpp:test",
"program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js",
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "node"
}
],
"compounds": []
}
}

8
scripts/format Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -e
cd "$(dirname $(dirname "$0"))" || exit
source $NEKO_SDK/activate
source $HAXE_SDK/activate
haxelib install formatter
haxelib run formatter -s ./src/common/haxe -s ./src/app/haxe -s ./src/server/haxe

8
scripts/lint Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -e
cd "$(dirname $(dirname "$0"))" || exit
source $NEKO_SDK/activate
source $HAXE_SDK/activate
haxelib install formatter
haxelib run formatter --check -s ./src/common/haxe -s ./src/app/haxe -s ./src/server/haxe

5
scripts/setup Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e
cd "$(dirname $(dirname "$0"))" || exit
npm ci

View File

@@ -3,36 +3,37 @@ package ru.m;
#if macro #if macro
import haxe.macro.Context; import haxe.macro.Context;
import haxe.macro.Expr; import haxe.macro.Expr;
using haxe.macro.Tools; using haxe.macro.Tools;
#end #end
class AbstractEnumTools { class AbstractEnumTools {
public static macro function getValues(typePath:Expr):Expr { public static macro function getValues(typePath:Expr):Expr {
// Get the type from a given expression converted to string. // Get the type from a given expression converted to string.
// This will work for identifiers and field access which is what we need, // This will work for identifiers and field access which is what we need,
// it will also consider local imports. If expression is not a valid type path or type is not found, // it will also consider local imports. If expression is not a valid type path or type is not found,
// compiler will give a error here. // compiler will give a error here.
var type = Context.getType(typePath.toString()); var type = Context.getType(typePath.toString());
// Switch on the type and check if it's an abstract with @:enum metadata // Switch on the type and check if it's an abstract with @:enum metadata
switch (type.follow()) { switch (type.follow()) {
case TAbstract(_.get() => ab, _) if (ab.meta.has(":enum")): case TAbstract(_.get() => ab, _) if (ab.meta.has(":enum")):
// @:enum abstract values are actually static fields of the abstract implementation class, // @:enum abstract values are actually static fields of the abstract implementation class,
// marked with @:enum and @:impl metadata. We generate an array of expressions that access those fields. // marked with @:enum and @:impl metadata. We generate an array of expressions that access those fields.
// Note that this is a bit of implementation detail, so it can change in future Haxe versions, but it's been // Note that this is a bit of implementation detail, so it can change in future Haxe versions, but it's been
// stable so far. // stable so far.
var valueExprs = []; var valueExprs = [];
for (field in ab.impl.get().statics.get()) { for (field in ab.impl.get().statics.get()) {
if (field.meta.has(":enum") && field.meta.has(":impl")) { if (field.meta.has(":enum") && field.meta.has(":impl")) {
var fieldName = field.name; var fieldName = field.name;
valueExprs.push(macro $typePath.$fieldName); valueExprs.push(macro $typePath.$fieldName);
} }
}
// Return collected expressions as an array declaration.
return macro $a{valueExprs};
default:
// The given type is not an abstract, or doesn't have @:enum metadata, show a nice error message.
throw new Error(type.toString() + " should be @:enum abstract", typePath.pos);
} }
// Return collected expressions as an array declaration.
return macro $a{valueExprs};
default:
// The given type is not an abstract, or doesn't have @:enum metadata, show a nice error message.
throw new Error(type.toString() + " should be @:enum abstract", typePath.pos);
} }
}
} }

View File

@@ -6,72 +6,68 @@ import flash.Lib;
import hw.signal.Signal; import hw.signal.Signal;
enum abstract Platform(String) from String to String { enum abstract Platform(String) from String to String {
var ANDROID = "android"; var ANDROID = "android";
var LINUX = "linux"; var LINUX = "linux";
var WINDOWS = "windows"; var WINDOWS = "windows";
var FLASH = "flash"; var FLASH = "flash";
var HTML5 = "html5"; var HTML5 = "html5";
var UNKNOWN = "unknown"; var UNKNOWN = "unknown";
} }
class Device { class Device {
public static var platform(get, null):Platform;
public static var platform(get, null):Platform; private static function get_platform():Platform {
#if android
return ANDROID;
#elseif linux
return LINUX;
#elseif windows
return WINDOWS;
#elseif flash
return FLASH;
#elseif html5
return HTML5;
#else
return UNKNOWN;
#end
}
private static function get_platform():Platform { private static var MOBILES(default, never):Array<String> = ["Android", "webOS", "iPhone", "iPad", "iPod", "BlackBerry", "Windows Phone",];
#if android
return ANDROID;
#elseif linux
return LINUX;
#elseif windows
return WINDOWS;
#elseif flash
return FLASH;
#elseif html5
return HTML5;
#else
return UNKNOWN;
#end
}
private static var MOBILES(default, never):Array<String> = [ public static function isMobile():Bool {
"Android", "webOS", "iPhone", "iPad", "iPod", "BlackBerry", "Windows Phone", #if android
]; return true;
#elseif js
public static function isMobile():Bool { var userAgent = js.Browser.navigator.userAgent;
#if android for (mobile in MOBILES) {
if (userAgent.indexOf(mobile) > -1) {
return true; return true;
#elseif js }
var userAgent = js.Browser.navigator.userAgent;
for (mobile in MOBILES) {
if (userAgent.indexOf(mobile) > -1) {
return true;
}
}
return false;
#else
return false;
#end
} }
return false;
#else
return false;
#end
}
public static var fullScreenSignal(get, null):Signal<Bool>; public static var fullScreenSignal(get, null):Signal<Bool>;
private static function get_fullScreenSignal():Signal<Bool> { private static function get_fullScreenSignal():Signal<Bool> {
if (fullScreenSignal == null) { if (fullScreenSignal == null) {
fullScreenSignal = new Signal(); fullScreenSignal = new Signal();
Lib.current.stage.addEventListener(FullScreenEvent.FULL_SCREEN, (event:FullScreenEvent) -> fullScreenSignal.emit(event.fullScreen)); Lib.current.stage.addEventListener(FullScreenEvent.FULL_SCREEN, (event:FullScreenEvent) -> fullScreenSignal.emit(event.fullScreen));
}
return fullScreenSignal;
} }
return fullScreenSignal;
}
public static var fullScreenSupport(get, never):Bool; public static var fullScreenSupport(get, never):Bool;
public static function get_fullScreenSupport():Bool { public static function get_fullScreenSupport():Bool {
return Lib.current.stage.allowsFullScreen; return Lib.current.stage.allowsFullScreen;
} }
public static function toggleFullScreen():Void { public static function toggleFullScreen():Void {
Lib.current.stage.displayState = Lib.current.stage.displayState == StageDisplayState.NORMAL ? Lib.current.stage.displayState = Lib.current.stage.displayState == StageDisplayState.NORMAL ? StageDisplayState.FULL_SCREEN : StageDisplayState.NORMAL;
StageDisplayState.FULL_SCREEN : StageDisplayState.NORMAL; }
}
} }

View File

@@ -5,48 +5,39 @@ import openfl.net.FileReference;
import openfl.utils.ByteArray; import openfl.utils.ByteArray;
class Callback<T> { class Callback<T> {
private var success:T -> Void; private var success:T->Void;
private var fail:Dynamic -> Void; private var fail:Dynamic->Void;
public function new(success: T -> Void, fail: Dynamic -> Void) { public function new(success:T->Void, fail:Dynamic->Void) {
this.success = success; this.success = success;
this.fail = fail; this.fail = fail;
} }
public function onSuccess(result:T):Void { public function onSuccess(result:T):Void {
this.success(result); this.success(result);
} }
public function onFail(error:Dynamic):Void { public function onFail(error:Dynamic):Void {
this.fail(error); this.fail(error);
} }
} }
class ModernFileReference extends FileReference { class ModernFileReference extends FileReference {
#if android #if android
private static var fileUtilBrowse = lime.system.JNI.createStaticMethod( private static var fileUtilBrowse = lime.system.JNI.createStaticMethod("ru.m.android.FileUtil", "browse", "(Lorg/haxe/lime/HaxeObject;)V");
"ru.m.android.FileUtil", #end
"browse",
"(Lorg/haxe/lime/HaxeObject;)V"
);
#end
override public function browse(?typeFilter:Array<FileFilter>):Bool { override public function browse(?typeFilter:Array<FileFilter>):Bool {
#if android #if android
fileUtilBrowse(new Callback<haxe.io.BytesData>( fileUtilBrowse(new Callback<haxe.io.BytesData>(function(result:haxe.io.BytesData):Void {
function(result:haxe.io.BytesData):Void { data = ByteArray.fromBytesData(result);
data = ByteArray.fromBytesData(result); dispatchEvent(new flash.events.Event(flash.events.Event.COMPLETE));
dispatchEvent(new flash.events.Event(flash.events.Event.COMPLETE)); }, function(error:Dynamic):Void {
}, dispatchEvent(new flash.events.IOErrorEvent(flash.events.IOErrorEvent.IO_ERROR, false, false, Std.string(error)));
function(error:Dynamic):Void { }));
dispatchEvent(new flash.events.IOErrorEvent( return true;
flash.events.IOErrorEvent.IO_ERROR, false, false, Std.string(error) #else
)); return super.browse(typeFilter);
} #end
)); }
return true;
#else
return super.browse(typeFilter);
#end
}
} }

View File

@@ -0,0 +1,76 @@
package ru.m.api;
import hw.net.JsonLoader;
import promhx.Promise;
typedef PixabayImage = {
var id:Int;
var largeImageURL:String;
var webformatURL:String;
var previewURL:String;
}
typedef PixabayResponse = {
var total:Int;
var totalHits:Int;
var hits:Array<PixabayImage>;
}
enum abstract PixabayCategory(String) from String to String {
var FASHION = "fashion";
var NATURE = "nature";
var BACKGROUNDS = "backgrounds";
var SCIENCE = "science";
var EDUCATION = "education";
var PEOPLE = "people";
var FEELINGS = "feelings";
var RELIGION = "religion";
var HEALTH = "health";
var PLACES = "places";
var ANIMALS = "animals";
var INDUSTRY = "industry";
var FOOD = "food";
var COMPUTER = "computer";
var SPORTS = "sports";
var TRANSPORTATION = "transportation";
var TRAVEL = "travel";
var BUILDINGS = "buildings";
var BUSINESS = "business";
var MUSIC = "music";
}
enum abstract PixabayImageType(String) from String to String {
var ALL = "all";
var PHOTO = "photo";
var ILLUSTRATION = "illustration";
var VECTOR = "vector";
}
class PixabayApi {
private var baseUrl:String = "https://pixabay.com/api/";
private var key:String;
public function new(key:String) {
this.key = key;
}
private function buildRequest(queryMap:Map<String, Dynamic>):String {
queryMap.set("key", key);
var query = [for (k in queryMap.keys()) '${k}=${queryMap.get(k)}'].join("&");
return '${baseUrl}?${query}';
}
public function getPage(page:Int, perPage:Int, category:PixabayCategory = PixabayCategory.NATURE):Promise<PixabayResponse> {
return new JsonLoader<PixabayResponse>().GET(buildRequest([
"category" => category,
"image_type" => PixabayImageType.PHOTO,
"editors_choice" => true,
"per_page" => perPage,
"page" => page,
]));
}
public function get(id:Int):Promise<PixabayImage> {
return new JsonLoader<PixabayResponse>().GET(buildRequest(["id" => id])).then((response:PixabayResponse) -> response.hits[0]);
}
}

View File

@@ -4,59 +4,55 @@ import hw.net.JsonLoader;
import promhx.Promise; import promhx.Promise;
typedef UnsplashImage = { typedef UnsplashImage = {
var id:String; var id:String;
var width:Int; var width:Int;
var height:Int; var height:Int;
var urls:{ var urls:{
raw:String, raw:String,
full:String, full:String,
regular:String, regular:String,
small:String, small:String,
thumb:String, thumb:String,
}; };
} }
typedef UnsplashResponse = { typedef UnsplashResponse = {
var total: Int; var total:Int;
var total_pages: Int; var total_pages:Int;
var results: Array<UnsplashImage>; var results:Array<UnsplashImage>;
} }
class UnsplashApi { class UnsplashApi {
private var baseUrl:String = "https://api.unsplash.com"; private var baseUrl:String = "https://api.unsplash.com";
private var key:String; private var key:String;
public function new(key:String) { public function new(key:String) {
this.key = key; this.key = key;
} }
private function buildQuery(queryMap:Map<String, Dynamic>):String { private function buildQuery(queryMap:Map<String, Dynamic>):String {
return [for (k in queryMap.keys()) '${k}=${queryMap.get(k)}'].join("&"); return [for (k in queryMap.keys()) '${k}=${queryMap.get(k)}'].join("&");
} }
private function buildRequest(queryMap:Map<String, Dynamic>):String { private function buildRequest(queryMap:Map<String, Dynamic>):String {
queryMap.set("client_id", key); queryMap.set("client_id", key);
var query = buildQuery(queryMap); var query = buildQuery(queryMap);
return '${baseUrl}/search/photos?${query}'; return '${baseUrl}/search/photos?${query}';
} }
public function getPage(page:Int, perPage:Int, query:String):Promise<UnsplashResponse> { public function getPage(page:Int, perPage:Int, query:String):Promise<UnsplashResponse> {
return new JsonLoader<UnsplashResponse>() return new JsonLoader<UnsplashResponse>().GET(buildRequest([
.GET(buildRequest([ "per_page" => perPage,
"per_page" => perPage, "page" => page,
"page" => page, "order_by" => "relevant",
"order_by" => "relevant", "orientation" => "landscape",
"orientation" => "landscape", "query" => query,
"query" => query, ]));
])); }
}
public function get(id:String):Promise<UnsplashImage> { public function get(id:String):Promise<UnsplashImage> {
var queryMap = [ var queryMap = ["client_id" => key,];
"client_id" => key, var query = buildQuery(queryMap);
]; return new JsonLoader<UnsplashImage>().GET('${baseUrl}/photos/${id}?${query}');
var query = buildQuery(queryMap); }
return new JsonLoader<UnsplashImage>()
.GET('${baseUrl}/photos/${id}?${query}');
}
} }

View File

@@ -5,34 +5,34 @@ import haxe.crypto.Md5;
import haxe.io.Bytes; import haxe.io.Bytes;
class StorageCache { class StorageCache {
private static var DATA_KEY:String = "data"; private static var DATA_KEY:String = "data";
private var name:String; private var name:String;
public function new() { public function new() {
name = "cache"; name = "cache";
}
private function resolveDataObject(key:String):SharedObject {
return SharedObject.getLocal('${name}/${Md5.encode(key)}');
}
public function exists(key:String):Bool {
var dataObject = resolveDataObject(key);
return Reflect.hasField(dataObject.data, DATA_KEY);
}
public function get(key:String):Null<Bytes> {
var dataObject = resolveDataObject(key);
if (Reflect.hasField(dataObject.data, DATA_KEY)) {
return Bytes.ofHex(Reflect.field(dataObject.data, DATA_KEY));
} }
return null;
}
private function resolveDataObject(key:String):SharedObject { public function set(key:String, data:Bytes):Void {
return SharedObject.getLocal('${name}/${Md5.encode(key)}'); var dataObject = resolveDataObject(key);
} dataObject.setProperty(DATA_KEY, data.toHex());
dataObject.flush();
public function exists(key:String):Bool { }
var dataObject = resolveDataObject(key);
return Reflect.hasField(dataObject.data, DATA_KEY);
}
public function get(key:String):Null<Bytes> {
var dataObject = resolveDataObject(key);
if (Reflect.hasField(dataObject.data, DATA_KEY)) {
return Bytes.ofHex(Reflect.field(dataObject.data, DATA_KEY));
}
return null;
}
public function set(key:String, data:Bytes):Void {
var dataObject = resolveDataObject(key);
dataObject.setProperty(DATA_KEY, data.toHex());
dataObject.flush();
}
} }

View File

@@ -3,31 +3,32 @@ package ru.m.data;
import promhx.Promise; import promhx.Promise;
typedef Filter = Map<String, Dynamic>; typedef Filter = Map<String, Dynamic>;
typedef Order = Array<{ typedef Order = Array<{
var field:String; var field:String;
@:optional var reverse:Bool; @:optional var reverse:Bool;
}>; }>;
typedef Page = { typedef Page = {
var index:Int; var index:Int;
var count:Int; var count:Int;
@:optional var filter:Filter; @:optional var filter:Filter;
@:optional var order:Order; @:optional var order:Order;
} }
typedef DataPage<D> = { typedef DataPage<D> = {
var page:Page; var page:Page;
var data:Array<D>; var data:Array<D>;
var total:Int; var total:Int;
} }
interface DataSource<D> { interface DataSource<D> {
public function getPage(page:Page):Promise<DataPage<D>>; public function getPage(page:Page):Promise<DataPage<D>>;
} }
interface DataStorage<D, I> extends DataSource<D> { interface DataStorage<D, I> extends DataSource<D> {
public function getIndexPage(page:Page):Promise<DataPage<I>>; public function getIndexPage(page:Page):Promise<DataPage<I>>;
public function get(id:I):Promise<D>; public function get(id:I):Promise<D>;
public function save(item:D):Promise<D>; public function save(item:D):Promise<D>;
public function delete(id:I):Promise<Bool>; public function delete(id:I):Promise<Bool>;
} }

View File

@@ -5,49 +5,49 @@ import flash.display.GraphicsPathCommand;
import flash.Vector; import flash.Vector;
enum DrawCommand { enum DrawCommand {
MOVE_TO(x:Float, y:Float); MOVE_TO(x:Float, y:Float);
LINE_TO(x:Float, y:Float); LINE_TO(x:Float, y:Float);
CURVE_TO(cx:Float, cy:Float, ax:Float, ay:Float); CURVE_TO(cx:Float, cy:Float, ax:Float, ay:Float);
} }
class DrawPath { class DrawPath {
public var commands(default, null):Array<DrawCommand>; public var commands(default, null):Array<DrawCommand>;
public function new(commands:Array<DrawCommand> = null) { public function new(commands:Array<DrawCommand> = null) {
this.commands = commands != null ? commands : []; this.commands = commands != null ? commands : [];
} }
public function draw(graphics:Graphics):Void { public function draw(graphics:Graphics):Void {
var commands = new Vector<GraphicsPathCommand>(); var commands = new Vector<GraphicsPathCommand>();
var data = new Vector<Float>(); var data = new Vector<Float>();
for (command in this.commands) { for (command in this.commands) {
switch command { switch command {
case MOVE_TO(x, y): case MOVE_TO(x, y):
commands.push(GraphicsPathCommand.MOVE_TO); commands.push(GraphicsPathCommand.MOVE_TO);
data.push(x); data.push(x);
data.push(y); data.push(y);
case LINE_TO(x, y): case LINE_TO(x, y):
commands.push(GraphicsPathCommand.LINE_TO); commands.push(GraphicsPathCommand.LINE_TO);
data.push(x); data.push(x);
data.push(y); data.push(y);
case CURVE_TO(cx, cy, ax, ay): case CURVE_TO(cx, cy, ax, ay):
commands.push(GraphicsPathCommand.CURVE_TO); commands.push(GraphicsPathCommand.CURVE_TO);
data.push(cx); data.push(cx);
data.push(cy); data.push(cy);
data.push(ax); data.push(ax);
data.push(ay); data.push(ay);
} }
}
graphics.drawPath(commands, data);
} }
graphics.drawPath(commands, data);
}
public function move(mx:Float, my:Float):DrawPath { public function move(mx:Float, my:Float):DrawPath {
var result = new DrawPath(); var result = new DrawPath();
result.commands = commands.map(command -> switch command { result.commands = commands.map(command -> switch command {
case MOVE_TO(x, y): MOVE_TO(x + mx, y + my); case MOVE_TO(x, y): MOVE_TO(x + mx, y + my);
case LINE_TO(x, y): LINE_TO(x + mx, y + my); case LINE_TO(x, y): LINE_TO(x + mx, y + my);
case CURVE_TO(cx, cy, ax, ay): CURVE_TO(cx + mx, cy + my, ax + mx, ay + my); case CURVE_TO(cx, cy, ax, ay): CURVE_TO(cx + mx, cy + my, ax + mx, ay + my);
}); });
return result; return result;
} }
} }

View File

@@ -2,6 +2,4 @@ package ru.m.event;
import flash.events.Event; import flash.events.Event;
class GestureEvent extends Event { class GestureEvent extends Event {}
}

View File

@@ -5,69 +5,68 @@ import flash.display.DisplayObject;
import flash.events.TouchEvent; import flash.events.TouchEvent;
typedef Touch = { typedef Touch = {
var id:Int; var id:Int;
var point:Point; var point:Point;
} }
class GestureManager { class GestureManager {
private var target:DisplayObject;
private var touchesMap:Map<Int, Touch>;
private var touches:Array<Touch>;
private var distance:Float;
private var target:DisplayObject; public function new(target:DisplayObject) {
private var touchesMap:Map<Int, Touch>; this.target = target;
private var touches:Array<Touch>; touchesMap = new Map();
private var distance:Float; touches = new Array();
target.addEventListener(TouchEvent.TOUCH_BEGIN, onTouchBegin);
target.addEventListener(TouchEvent.TOUCH_MOVE, onTouchMove);
target.addEventListener(TouchEvent.TOUCH_END, onTouchEnd);
}
public function new(target:DisplayObject) { private function addTouch(id:Int, point:Point):Void {
this.target = target; var touch:Touch = {id: id, point: point};
touchesMap = new Map(); touchesMap.set(touch.id, touch);
touches = new Array(); touches.push(touch);
target.addEventListener(TouchEvent.TOUCH_BEGIN, onTouchBegin); if (touches.length == 2) {
target.addEventListener(TouchEvent.TOUCH_MOVE, onTouchMove); distance = Point.distance(touches[0].point, touches[1].point);
target.addEventListener(TouchEvent.TOUCH_END, onTouchEnd);
} }
}
private function addTouch(id:Int, point:Point):Void { private function updateTouch(id:Int, point:Point):Void {
var touch:Touch = {id: id, point: point}; touchesMap.get(id).point = point;
touchesMap.set(touch.id, touch); if (touches.length == 2) {
touches.push(touch); var newDistance = Point.distance(touches[0].point, touches[1].point);
if (touches.length == 2) { var event = new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM);
distance = Point.distance(touches[0].point, touches[1].point); event.zoom = (newDistance - distance) * 0.001;
} distance = newDistance;
target.dispatchEvent(event);
} }
}
private function updateTouch(id:Int, point:Point):Void { private function removeTouch(id:Int):Void {
touchesMap.get(id).point = point; touches.remove(touchesMap.get(id));
if (touches.length == 2) { touchesMap.remove(id);
var newDistance = Point.distance(touches[0].point, touches[1].point); }
var event = new ZoomGestureEvent(ZoomGestureEvent.GESTURE_ZOOM);
event.zoom = (newDistance - distance) * 0.001;
distance = newDistance;
target.dispatchEvent(event);
}
}
private function removeTouch(id:Int):Void { private function onTouchBegin(event:TouchEvent):Void {
touches.remove(touchesMap.get(id)); addTouch(event.touchPointID, new Point(event.stageX, event.stageY));
touchesMap.remove(id); }
}
private function onTouchBegin(event:TouchEvent):Void { private function onTouchMove(event:TouchEvent):Void {
addTouch(event.touchPointID, new Point(event.stageX, event.stageY)); updateTouch(event.touchPointID, new Point(event.stageX, event.stageY));
} }
private function onTouchMove(event:TouchEvent):Void { private function onTouchEnd(event:TouchEvent):Void {
updateTouch(event.touchPointID, new Point(event.stageX, event.stageY)); removeTouch(event.touchPointID);
} }
private function onTouchEnd(event:TouchEvent):Void { public function dispose():Void {
removeTouch(event.touchPointID); if (target != null) {
} target.removeEventListener(TouchEvent.TOUCH_BEGIN, onTouchBegin);
target.removeEventListener(TouchEvent.TOUCH_MOVE, onTouchMove);
public function dispose():Void { target.removeEventListener(TouchEvent.TOUCH_END, onTouchEnd);
if (target != null) { target = null;
target.removeEventListener(TouchEvent.TOUCH_BEGIN, onTouchBegin);
target.removeEventListener(TouchEvent.TOUCH_MOVE, onTouchMove);
target.removeEventListener(TouchEvent.TOUCH_END, onTouchEnd);
target = null;
}
} }
}
} }

View File

@@ -1,7 +1,7 @@
package ru.m.event; package ru.m.event;
class ZoomGestureEvent extends GestureEvent { class ZoomGestureEvent extends GestureEvent {
public static var GESTURE_ZOOM(default, never):String = "gesture_zoom"; public static var GESTURE_ZOOM(default, never):String = "gesture_zoom";
public var zoom(default, default):Float; public var zoom(default, default):Float;
} }

View File

@@ -1,79 +0,0 @@
package ru.m.pixabay;
import hw.net.JsonLoader;
import promhx.Promise;
typedef PixabayImage = {
var id:Int;
var largeImageURL:String;
var webformatURL:String;
var previewURL:String;
}
typedef PixabayResponse = {
var total:Int;
var totalHits:Int;
var hits:Array<PixabayImage>;
}
enum abstract PixabayCategory(String) from String to String {
var FASHION = "fashion";
var NATURE = "nature";
var BACKGROUNDS = "backgrounds";
var SCIENCE = "science";
var EDUCATION = "education";
var PEOPLE = "people";
var FEELINGS = "feelings";
var RELIGION = "religion";
var HEALTH = "health";
var PLACES = "places";
var ANIMALS = "animals";
var INDUSTRY = "industry";
var FOOD = "food";
var COMPUTER = "computer";
var SPORTS = "sports";
var TRANSPORTATION = "transportation";
var TRAVEL = "travel";
var BUILDINGS = "buildings";
var BUSINESS = "business";
var MUSIC = "music";
}
enum abstract PixabayImageType(String) from String to String {
var ALL = "all";
var PHOTO = "photo";
var ILLUSTRATION = "illustration";
var VECTOR = "vector";
}
class PixabayApi {
private var baseUrl:String = "https://pixabay.com/api/";
private var key:String;
public function new(key:String) {
this.key = key;
}
private function buildRequest(queryMap:Map<String, Dynamic>):String {
queryMap.set("key", key);
var query = [for (k in queryMap.keys()) '${k}=${queryMap.get(k)}'].join("&");
return '${baseUrl}?${query}';
}
public function getPage(page:Int, perPage:Int, category:PixabayCategory = PixabayCategory.NATURE):Promise<PixabayResponse> {
return new JsonLoader<PixabayResponse>()
.GET(buildRequest([
"category" => category,
"image_type" => PixabayImageType.PHOTO,
"editors_choice" => true,
"per_page" => perPage,
"page" => page,
]));
}
public function get(id:Int):Promise<PixabayImage> {
return new JsonLoader<PixabayResponse>()
.GET(buildRequest(["id" => id]))
.then((response:PixabayResponse) -> response.hits[0]);
}
}

View File

@@ -8,37 +8,36 @@ import promhx.Deferred;
import promhx.Promise; import promhx.Promise;
typedef FileContent = { typedef FileContent = {
var name:String; var name:String;
var content:Bytes; var content:Bytes;
} }
class FileUtil { class FileUtil {
public static function browse():Promise<FileContent> {
var d = new Deferred<FileContent>();
var file = new ModernFileReference();
file.addEventListener(Event.SELECT, (event:Event) -> {
cast(event.target, ModernFileReference).load();
});
file.addEventListener(IOErrorEvent.IO_ERROR, (event:IOErrorEvent) -> {
d.throwError(event);
});
file.addEventListener(ProgressEvent.PROGRESS, (event:ProgressEvent) -> {
// trace('progress', '${event}');
});
file.addEventListener(Event.COMPLETE, (event:Event) -> {
var f:ModernFileReference = cast event.target;
d.resolve({
name: f.name,
content: Bytes.ofData(f.data),
});
});
file.browse();
return d.promise();
}
public static function browse():Promise<FileContent> { public static function save(content:FileContent):Void {
var d = new Deferred<FileContent>(); var file = new ModernFileReference();
var file = new ModernFileReference(); file.save(content.content.getData(), content.name);
file.addEventListener(Event.SELECT, (event:Event) -> { }
cast(event.target, ModernFileReference).load();
});
file.addEventListener(IOErrorEvent.IO_ERROR, (event:IOErrorEvent) -> {
d.throwError(event);
});
file.addEventListener(ProgressEvent.PROGRESS, (event:ProgressEvent) -> {
//trace('progress', '${event}');
});
file.addEventListener(Event.COMPLETE, (event:Event) -> {
var f:ModernFileReference = cast event.target;
d.resolve({
name: f.name,
content: Bytes.ofData(f.data),
});
});
file.browse();
return d.promise();
}
public static function save(content:FileContent):Void {
var file = new ModernFileReference();
file.save(content.content.getData(), content.name);
}
} }

View File

@@ -11,17 +11,16 @@ import promhx.Deferred;
import promhx.Promise; import promhx.Promise;
class ImageUtil { class ImageUtil {
public static function bytesToImage(bytes:Bytes):Promise<BitmapData> {
public static function bytesToImage(bytes:Bytes):Promise<BitmapData> { var def = new Deferred();
var def = new Deferred(); var loader = new Loader();
var loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, (event:Event) -> {
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, (event:Event) -> { def.resolve(cast(cast(event.target, LoaderInfo).content, Bitmap).bitmapData);
def.resolve(cast(cast(event.target, LoaderInfo).content, Bitmap).bitmapData); });
}); loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, (event:IOErrorEvent) -> {
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, (event:IOErrorEvent) -> { def.throwError(event);
def.throwError(event); });
}); loader.loadBytes(bytes);
loader.loadBytes(bytes); return def.promise();
return def.promise(); }
}
} }

View File

@@ -15,25 +15,24 @@ import ru.m.puzzlez.view.PuzzlezAppView;
import ru.m.update.Updater; import ru.m.update.Updater;
class PuzzlezApp { class PuzzlezApp {
@:provide static var updater:Updater;
@:provide static var sourceBundle:ImageSourceBundle;
@:provide static var updater:Updater; public static function main() {
@:provide static var sourceBundle:ImageSourceBundle; // ToDo: fix @:provide macro
Settings;
public static function main() { IPartBuilder;
// ToDo: fix @:provide macro GameStorage;
Settings; sourceBundle.register(new AssetImageSource());
IPartBuilder; sourceBundle.register(new FileImageSource());
GameStorage; sourceBundle.register(new PixabayImageSource());
sourceBundle.register(new AssetImageSource()); sourceBundle.register(new UnsplashImageSource());
sourceBundle.register(new FileImageSource()); L.push(new TraceLogger());
sourceBundle.register(new PixabayImageSource()); updater = new Updater(Const.instance.VERSION, "https://shmyga.ru/repo/puzzlez/packages.json");
sourceBundle.register(new UnsplashImageSource()); var app = new App();
L.push(new TraceLogger()); app.theme = new PuzzlezTheme();
updater = new Updater(Const.instance.VERSION, "https://shmyga.ru/repo/puzzlez/packages.json"); app.icon = openfl.Assets.getBitmapData("resources/icon.png");
var app = new App(); app.view = new PuzzlezAppView();
app.theme = new PuzzlezTheme(); L.d("Puzzlez", "started");
app.icon = openfl.Assets.getBitmapData("resources/icon.png"); }
app.view = new PuzzlezAppView();
L.d("Puzzlez", "started");
}
} }

View File

@@ -10,75 +10,60 @@ import openfl.Assets;
import ru.m.skin.ButtonSVGSkin; import ru.m.skin.ButtonSVGSkin;
class PuzzlezTheme extends Theme { class PuzzlezTheme extends Theme {
private static var ICONS:Map<String, String> = [
"close" => "times-circle-solid.svg",
"setting" => "cog-solid.svg",
"image" => "image-polaroid.svg",
"lock" => "lock-alt-solid.svg",
"restore" => "window-restore-solid.svg",
"compress" => "compress-solid.svg",
"expand" => "expand-solid.svg",
];
private static var ICONS:Map<String, String> = [ public function new() {
"close" => "times-circle-solid.svg", super({embed: true}, {light: "gray"}, {base: "4h"});
"setting" => "cog-solid.svg", register(new Style("frame", ["geometry.padding" => Box.fromFloat(8),]));
"image" => "image-polaroid.svg", register(new Style("view", [
"lock" => "lock-alt-solid.svg", "skin.background.color" => colors.light,
"restore" => "window-restore-solid.svg", "skin.border.color" => colors.border,
"compress" => "compress-solid.svg", "geometry.padding" => Box.fromFloat(3),
"expand" => "expand-solid.svg", "geometry.width" => SizeValue.fromString("50h"),
]; "geometry.height" => SizeValue.fromString("40h"),
]));
public function new() { register(new Style("text.error", ["font.color" => Color.fromString("red"),], "text"));
super({embed: true}, {light: "gray"}, {base: "4h"}); register(new Style("icon", [
register(new Style("frame", [ "geometry.width" => SizeValue.fromString("8h"),
"geometry.padding" => Box.fromFloat(8), "geometry.height" => SizeValue.fromString("8h"),
])); "skin" => function() return new ButtonSVGSkin(),
register(new Style("view", [ "skin.color" => colors.light,
"skin.background.color" => colors.light, ]));
"skin.border.color" => colors.border, for (key in ICONS.keys()) {
"geometry.padding" => Box.fromFloat(3), register(new Style('icon.${key}', ["skin.svg" => Assets.getText('resources/icon/${ICONS.get(key)}'),]));
"geometry.width" => SizeValue.fromString("50h"),
"geometry.height" => SizeValue.fromString("40h"),
]));
register(new Style("text.error", [
"font.color" => Color.fromString("red"),
], "text"));
register(new Style("icon", [
"geometry.width" => SizeValue.fromString("8h"),
"geometry.height" => SizeValue.fromString("8h"),
"skin" => function() return new ButtonSVGSkin(),
"skin.color" => colors.light,
]));
for (key in ICONS.keys()) {
register(new Style('icon.${key}', [
"skin.svg" => Assets.getText('resources/icon/${ICONS.get(key)}'),
]));
}
register(new Style("icon.small", [
"geometry.width" => SizeValue.fromString("6h"),
"geometry.height" => SizeValue.fromString("6h"),
]));
register(new Style("icon.red", [
"skin.color" => 0xcc0000,
]));
register(new Style("icon.orange", [
"skin.color" => 0xcc5500,
]));
register(new Style("icon.green", [
"skin.color" => 0x00ff00,
]));
register(new Style("icon.control", [
"geometry.hAlign" => HAlign.RIGHT,
"geometry.vAlign" => VAlign.TOP,
"geometry.margin" => Box.fromFloat(3),
]));
register(new Style("button.red", [
"skin.color" => 0xcc0000,
], "button"));
register(new Style("label.header", [
"font.size" => SizeValue.fromString("5h"),
"geometry.hAlign" => HAlign.CENTER,
"geometry.margin.top" => 10,
"geometry.margin.bottom" => 10,
], "label"));
register(new Style("button.background", [
"geometry.width" => SizeValue.fromString("10h"),
"geometry.height" => SizeValue.fromString("10h"),
]));
} }
register(new Style("icon.small", [
"geometry.width" => SizeValue.fromString("6h"),
"geometry.height" => SizeValue.fromString("6h"),
]));
register(new Style("icon.red", ["skin.color" => 0xcc0000,]));
register(new Style("icon.orange", ["skin.color" => 0xcc5500,]));
register(new Style("icon.green", ["skin.color" => 0x00ff00,]));
register(new Style("icon.control", [
"geometry.hAlign" => HAlign.RIGHT,
"geometry.vAlign" => VAlign.TOP,
"geometry.margin" => Box.fromFloat(3),
]));
register(new Style("button.red", ["skin.color" => 0xcc0000,], "button"));
register(new Style("label.header", [
"font.size" => SizeValue.fromString("5h"),
"geometry.hAlign" => HAlign.CENTER,
"geometry.margin.top" => 10,
"geometry.margin.bottom" => 10,
], "label"));
register(new Style("button.background", [
"geometry.width" => SizeValue.fromString("10h"),
"geometry.height" => SizeValue.fromString("10h"),
]));
}
} }

View File

@@ -4,10 +4,10 @@ import ru.m.puzzlez.proto.game.ImageId;
import ru.m.puzzlez.proto.game.GameState; import ru.m.puzzlez.proto.game.GameState;
enum GameAction { enum GameAction {
START(state:GameState); START(state:GameState);
} }
enum Action { enum Action {
GAME(state:GameState, ?delete:Bool); GAME(state:GameState, ?delete:Bool);
IMAGE(image:ImageId); IMAGE(image:ImageId);
} }

View File

@@ -5,6 +5,6 @@ import ru.m.data.DataSource;
import ru.m.puzzlez.proto.game.ImageId; import ru.m.puzzlez.proto.game.ImageId;
interface ImageSource extends DataSource<ImageId> { interface ImageSource extends DataSource<ImageId> {
public var id(default, never):String; public var id(default, never):String;
public function load(id:String, thumb:Bool = false):Promise<ImageValue>; public function load(id:String, thumb:Bool = false):Promise<ImageValue>;
} }

View File

@@ -4,7 +4,7 @@ import haxe.io.Bytes;
import flash.display.BitmapData; import flash.display.BitmapData;
enum ImageValue { enum ImageValue {
BITMAP(value:BitmapData); BITMAP(value:BitmapData);
BYTES(value:Bytes); BYTES(value:Bytes);
URL(value:String); URL(value:String);
} }

View File

@@ -9,50 +9,49 @@ import ru.m.puzzlez.core.ImageValue;
import ru.m.puzzlez.proto.game.ImageId; import ru.m.puzzlez.proto.game.ImageId;
abstract ImageData(ImageId) from ImageId { abstract ImageData(ImageId) from ImageId {
private static var cache:Map<String, Promise<BitmapData>> = new Map();
private static var storageCache:StorageCache = new StorageCache();
private static var cache:Map<String, Promise<BitmapData>> = new Map(); @:provide private var sourceBundle:ImageSourceBundle;
private static var storageCache:StorageCache = new StorageCache();
@:provide private var sourceBundle:ImageSourceBundle; public function new(image:ImageId) {
this = image;
}
public function new(image: ImageId) { @:from public static function fromImageId(value:ImageId):ImageData {
this = image; return new ImageData(value);
} }
@:from public static function fromImageId(value:ImageId):ImageData { public function withThumb():ImageData {
return new ImageData(value); return new ImageData(new ImageId().setSource(this.source).setId(this.id).setThumb(true));
} }
public function withThumb():ImageData { private function extractImage(key:String, value:ImageValue):Promise<BitmapData> {
return new ImageData(new ImageId().setSource(this.source).setId(this.id).setThumb(true)); return switch value {
} case ImageValue.BITMAP(value):
Promise.promise(value);
private function extractImage(key:String, value:ImageValue):Promise<BitmapData> { case ImageValue.BYTES(value):
return switch value { if (!storageCache.exists(key)) {
case ImageValue.BITMAP(value): storageCache.set(key, value);
Promise.promise(value);
case ImageValue.BYTES(value):
if (!storageCache.exists(key)) {
storageCache.set(key, value);
}
ImageUtil.bytesToImage(value);
case ImageValue.URL(value):
new BytesLoader().GET(value).pipe(bytes -> extractImage(key, ImageValue.BYTES(Bytes.ofData(bytes))));
} }
ImageUtil.bytesToImage(value);
case ImageValue.URL(value):
new BytesLoader().GET(value).pipe(bytes -> extractImage(key, ImageValue.BYTES(Bytes.ofData(bytes))));
} }
}
public function resolve():Promise<BitmapData> { public function resolve():Promise<BitmapData> {
var key = '${this.source}:${this.id}'; var key = '${this.source}:${this.id}';
if (this.thumb) { if (this.thumb) {
key = '${key}:thumb'; key = '${key}:thumb';
}
if (!cache.exists(key)) {
if (storageCache.exists(key)) {
cache.set(key, extractImage(key, ImageValue.BYTES(storageCache.get(key))));
} else {
cache.set(key, sourceBundle.get(this.source).load(this.id, this.thumb).pipe(value -> extractImage(key, value)));
}
}
return cache.get(key);
} }
if (!cache.exists(key)) {
if (storageCache.exists(key)) {
cache.set(key, extractImage(key, ImageValue.BYTES(storageCache.get(key))));
} else {
cache.set(key, sourceBundle.get(this.source).load(this.id, this.thumb).pipe(value -> extractImage(key, value)));
}
}
return cache.get(key);
}
} }

View File

@@ -3,20 +3,20 @@ package ru.m.puzzlez.image;
import ru.m.puzzlez.core.ImageSource; import ru.m.puzzlez.core.ImageSource;
@:provide class ImageSourceBundle { @:provide class ImageSourceBundle {
private var sources:Map<String, ImageSource>; private var sources:Map<String, ImageSource>;
public function new() { public function new() {
sources = new Map(); sources = new Map();
} }
public function register(source:ImageSource):Void { public function register(source:ImageSource):Void {
sources.set(source.id, source); sources.set(source.id, source);
} }
public function get(sourceId:String):ImageSource { public function get(sourceId:String):ImageSource {
if (!sources.exists(sourceId)) { if (!sources.exists(sourceId)) {
throw 'ImageSource "$sourceId" not registered'; throw 'ImageSource "$sourceId" not registered';
}
return sources.get(sourceId);
} }
return sources.get(sourceId);
}
} }

View File

@@ -22,99 +22,99 @@ 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 IDataSource<String, GameState> { @:provide class Network implements IDataSource<String, GameState> {
public var userSignal:Signal<User> = new Signal(); public var userSignal:Signal<User> = new Signal();
public var notificationSignal:Signal<NotificationResponse> = new Signal(); public var notificationSignal:Signal<NotificationResponse> = new Signal();
public var listSignal:Signal<GameListResponse> = new Signal(); public var listSignal:Signal<GameListResponse> = new Signal();
public var joinSignal:Signal<GameState> = new Signal(); public var joinSignal:Signal<GameState> = new Signal();
public var gameEventSignal:Signal<GameEvent> = new Signal(); public var gameEventSignal:Signal<GameEvent> = new Signal();
private var connection:IConnection<Request, Response>; private var connection:IConnection<Request, Response>;
private var storage:SharedObjectStorage; private var storage:SharedObjectStorage;
private static var USER_KEY = "user"; private static var USER_KEY = "user";
public function new() { public function new() {
storage = new SharedObjectStorage("network/2"); storage = new SharedObjectStorage("network/2");
connection = ConnectionFactory.buildClientConnection("127.0.0.1", 5000, Response); connection = ConnectionFactory.buildClientConnection("127.0.0.1", 5000, Response);
connection.handler.connect(onConnectionChange); connection.handler.connect(onConnectionChange);
connection.receiveHandler.connect(onReceivePacket); connection.receiveHandler.connect(onReceivePacket);
connection.connect().catchError(_ -> {}); connection.connect().catchError(_ -> {});
}
private function restoreUser():User {
if (storage.exists(USER_KEY)) {
return storage.read(USER_KEY);
} else {
return new User().setName('Anonimus #${IdUtil.generate()}');
} }
}
private function restoreUser():User { public function auth():Promise<User> {
if (storage.exists(USER_KEY)) { connection.send(new Request().setAuth(new AuthRequest().setUser(restoreUser())));
return storage.read(USER_KEY); return userSignal.next();
} else { }
return new User().setName('Anonimus #${IdUtil.generate()}');
}
}
public function auth():Promise<User> { public function createGame(preset:GamePreset):Promise<GameState> {
connection.send(new Request().setAuth(new AuthRequest().setUser(restoreUser()))); connection.send(new Request().setJoin(new GameJoinRequest().setPreset(preset)));
return userSignal.next(); return joinSignal.next();
} }
public function createGame(preset:GamePreset):Promise<GameState> { public function joinGame(gameId:String):Promise<GameState> {
connection.send(new Request().setJoin(new GameJoinRequest().setPreset(preset))); connection.send(new Request().setJoin(new GameJoinRequest().setGameId(gameId)));
return joinSignal.next(); return joinSignal.next();
} }
public function joinGame(gameId:String):Promise<GameState> { public function leaveGame():Void {
connection.send(new Request().setJoin(new GameJoinRequest().setGameId(gameId))); connection.send(new Request().setLeave(new GameLeaveRequest()));
return joinSignal.next(); }
}
public function leaveGame():Void { public function sendGameAction(action:GameAction):Void {
connection.send(new Request().setLeave(new GameLeaveRequest())); connection.send(new Request().setAction(new GameActionRequest().setActions([action])));
} }
public function sendGameAction(action:GameAction):Void { private function onConnectionChange(event:ConnectionEvent):Void {
connection.send(new Request().setAction(new GameActionRequest().setActions([action]))); L.i("network", '${event}');
switch event {
case CONNECTED:
auth().then(user -> storage.write(USER_KEY, user));
case DISCONNECTED:
//
case ERROR(error):
// ToDo: reconnect
} }
}
private function onConnectionChange(event:ConnectionEvent):Void { private function onReceivePacket(packet:Response):Void {
L.i("network", '${event}'); if (packet.hasAuth()) {
switch event { userSignal.emit(packet.auth.user);
case CONNECTED: } else if (packet.hasNotification()) {
auth().then(user -> storage.write(USER_KEY, user)); notificationSignal.emit(packet.notification);
case DISCONNECTED: } else if (packet.hasList()) {
// listSignal.emit(packet.list);
case ERROR(error): } else if (packet.hasJoin()) {
// ToDo: reconnect joinSignal.emit(packet.join.game);
} } else if (packet.hasEvent()) {
for (event in packet.event.events) {
gameEventSignal.emit(event);
}
} }
}
private function onReceivePacket(packet:Response):Void { public function getPage(page:Page):Promise<DataPage<GameState>> {
if (packet.hasAuth()) { connection.send(new Request().setList(new GameListRequest().setCount(page.count).setPage(page.index)));
userSignal.emit(packet.auth.user); return listSignal.next().then((list:GameListResponse) -> ({
} else if (packet.hasNotification()) { page: {
notificationSignal.emit(packet.notification); index: list.page,
} else if (packet.hasList()) { count: list.count,
listSignal.emit(packet.list); filter: null,
} else if (packet.hasJoin()) { order: null,
joinSignal.emit(packet.join.game); },
} else if (packet.hasEvent()) { total: list.total,
for (event in packet.event.events) { data: list.games,
gameEventSignal.emit(event); }));
} }
}
}
public function getPage(page:Page):Promise<DataPage<GameState>> { public function get(id:String):GameState {
connection.send(new Request().setList(new GameListRequest().setCount(page.count).setPage(page.index))); return null;
return listSignal.next().then((list:GameListResponse) -> ({ }
page: {
index: list.page,
count: list.count,
filter: null,
order: null,
},
total: list.total,
data: list.games,
}));
}
public function get(id:String):GameState {
return null;
}
} }

View File

@@ -8,36 +8,36 @@ import ru.m.puzzlez.proto.event.GameEvent;
import ru.m.puzzlez.proto.game.GameState; import ru.m.puzzlez.proto.game.GameState;
class NetworkGame implements IGame { class NetworkGame implements IGame {
public var state(default, null):GameState; public var state(default, null):GameState;
public var events(default, null):Signal<GameEvent>; public var events(default, null):Signal<GameEvent>;
@:provide private var network:Network; @:provide private var network:Network;
public function new(state:GameState) { public function new(state:GameState) {
this.state = state; this.state = state;
events = new Signal(); events = new Signal();
} }
public function action(action:GameAction):Void { public function action(action:GameAction):Void {
network.sendGameAction(action); network.sendGameAction(action);
} }
public function start():Void { public function start():Void {
events.emit(new GameEvent().setStart(new GameStart().setState(state))); events.emit(new GameEvent().setStart(new GameStart().setState(state)));
network.gameEventSignal.connect(onEvent); network.gameEventSignal.connect(onEvent);
} }
public function stop():Void { public function stop():Void {
network.gameEventSignal.disconnect(onEvent); network.gameEventSignal.disconnect(onEvent);
network.leaveGame(); network.leaveGame();
} }
public function dispose():Void { public function dispose():Void {
stop(); stop();
events.dispose(); events.dispose();
} }
private function onEvent(event:GameEvent):Void { private function onEvent(event:GameEvent):Void {
events.emit(event); events.emit(event);
} }
} }

View File

@@ -4,7 +4,7 @@ import hw.color.Color;
import ru.m.puzzlez.proto.game.ImageId; import ru.m.puzzlez.proto.game.ImageId;
enum Background { enum Background {
NONE; NONE;
COLOR(color:Color); COLOR(color:Color);
IMAGE(id:ImageId); IMAGE(id:ImageId);
} }

View File

@@ -7,66 +7,65 @@ import flash.geom.Matrix;
import ru.m.puzzlez.render.part.IPartBuilder; import ru.m.puzzlez.render.part.IPartBuilder;
class CompleteView extends Shape { class CompleteView extends Shape {
public var parts:Array<PartView>;
public var preset:GamePreset;
public var parts:Array<PartView>; @:provide static var builder:IPartBuilder;
public var preset:GamePreset;
@:provide static var builder:IPartBuilder; public function new() {
super();
parts = [];
}
public function new() { public function addChild(part:PartView):Void {
super(); if (part.parent != null) {
parts = []; part.parent.removeChild(part);
}
parts.push(part);
}
public function redraw():Void {
if (preset == null) {
return;
} }
public function addChild(part:PartView):Void { var partWidth = preset.imageRect.width / preset.grid.x;
if (part.parent != null) { var partHeight = preset.imageRect.height / preset.grid.y;
part.parent.removeChild(part);
}
parts.push(part);
}
public function redraw():Void { graphics.clear();
if (preset == null) { graphics.lineStyle(2, 0xCCCCCC);
return; graphics.beginFill(0x555555, 0.4);
} graphics.drawRect(0, 0, preset.imageRect.width, preset.imageRect.height);
graphics.endFill();
graphics.lineStyle();
var partWidth = preset.imageRect.width / preset.grid.x; for (partView in parts) {
var partHeight = preset.imageRect.height / preset.grid.y; var part = partView.part;
graphics.clear(); if (partView.currentImage != null) {
graphics.lineStyle(2, 0xCCCCCC); var image = partView.currentImage;
graphics.beginFill(0x555555, 0.4); var m = new Matrix();
graphics.drawRect(0, 0, preset.imageRect.width, preset.imageRect.height); m.translate(partView.x, partView.y);
graphics.beginBitmapFill(image, m, false, true);
graphics.drawRect(m.tx, m.ty, image.width, image.height);
graphics.endFill(); graphics.endFill();
}
var rect = cast(part.rect, RectangleExt).clone();
rect.x = part.point.x * part.rect.width;
rect.y = part.point.y * part.rect.height;
var path = builder.build(rect, part.bounds);
for (value in RenderUtil.borderSettings) {
graphics.lineStyle(1, value.color, value.opacity);
path.move(value.offset.x, value.offset.y).draw(graphics);
graphics.lineStyle(); graphics.lineStyle();
}
for (partView in parts) {
var part = partView.part;
if (partView.currentImage != null) {
var image = partView.currentImage;
var m = new Matrix();
m.translate(partView.x, partView.y);
graphics.beginBitmapFill(image, m, false, true);
graphics.drawRect(m.tx, m.ty, image.width, image.height);
graphics.endFill();
}
var rect = cast(part.rect, RectangleExt).clone();
rect.x = part.point.x * part.rect.width;
rect.y = part.point.y * part.rect.height;
var path = builder.build(rect, part.bounds);
for (value in RenderUtil.borderSettings) {
graphics.lineStyle(1, value.color, value.opacity);
path.move(value.offset.x, value.offset.y).draw(graphics);
graphics.lineStyle();
}
}
} }
}
public function clean():Void { public function clean():Void {
preset = null; preset = null;
parts = []; parts = [];
graphics.clear(); graphics.clear();
} }
} }

View File

@@ -6,9 +6,9 @@ import ru.m.puzzlez.proto.event.GameAction;
import ru.m.puzzlez.proto.event.GameEvent; 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>;
public var scale(get, set):Float; public var scale(get, set):Float;
public var manager(default, null):RenderManager; public var manager(default, null):RenderManager;
public function onGameEvent(event:GameEvent):Void; public function onGameEvent(event:GameEvent):Void;
} }

View File

@@ -8,33 +8,32 @@ import ru.m.puzzlez.proto.game.Part;
import ru.m.puzzlez.render.RenderUtil; import ru.m.puzzlez.render.RenderUtil;
typedef Result = { typedef Result = {
var part:Part; var part:Part;
var image:PartImage; var image:PartImage;
} }
class ImagePartBuilder { class ImagePartBuilder {
private var image:BitmapData;
private var image:BitmapData; public function new(image:BitmapData) {
this.image = image;
}
public function new(image:BitmapData) { private function buildPart(index:Int, count:Int, parts:Array<Part>, stream:PublicStream<Result>):Void {
this.image = image; for (i in index...index + count) {
if (i >= parts.length) {
return;
}
var part = parts[i];
var image = RenderUtil.cropImagePart(image, part);
stream.update({part: part, image: image});
} }
Timer.delay(() -> buildPart(index + count, count, parts, stream), 0);
}
private function buildPart(index:Int, count:Int, parts:Array<Part>, stream:PublicStream<Result>):Void { public function build(parts:Array<Part>):Stream<Result> {
for (i in index...index + count) { var stream = new PublicStream<Result>();
if (i >= parts.length) { Timer.delay(() -> buildPart(0, 5, parts, stream), 0);
return; return stream;
} }
var part = parts[i];
var image = RenderUtil.cropImagePart(image, part);
stream.update({part: part, image: image});
}
Timer.delay(() -> buildPart(index + count, count, parts, stream), 0);
}
public function build(parts:Array<Part>):Stream<Result> {
var stream = new PublicStream<Result>();
Timer.delay(() -> buildPart(0, 5, parts, stream), 0);
return stream;
}
} }

View File

@@ -12,116 +12,114 @@ import ru.m.puzzlez.wrap.PointExt;
import ru.m.puzzlez.wrap.RectangleExt; import ru.m.puzzlez.wrap.RectangleExt;
class PartView extends Sprite { class PartView extends Sprite {
public var id(default, null):Int;
public var part(default, null):Part;
public var position(default, set):Point;
public var completed(default, default):Bool;
public var target(default, null):Point;
public var id(default, null):Int; private function set_position(value:Point):Point {
public var part(default, null):Part; position = cast(value, PointExt).clone();
public var position(default, set):Point; refresh();
public var completed(default, default):Bool; return position;
public var target(default, null):Point; }
private function set_position(value:Point):Point { public var image(default, set):PartImage;
position = cast(value, PointExt).clone();
refresh(); private function set_image(value:PartImage):PartImage {
return position; image = value;
redraw();
refresh();
return image;
}
public var currentImage(get, never):BitmapData;
private function get_currentImage():BitmapData {
if (image == null) {
return null;
} }
return completed ? image.borderedImage : image.shadedImage;
}
public var image(default, set):PartImage; public var size(default, null):Point;
private function set_image(value:PartImage):PartImage { public var playerId(default, set):Null<String>;
image = value;
redraw(); private function set_playerId(value:Null<String>):Null<String> {
refresh(); if (playerId != value) {
return image; playerId = value;
redraw();
} }
return playerId;
}
public var currentImage(get, never):BitmapData; public function new(part:Part) {
super();
this.id = part.id;
this.part = part;
this.size = cast(part.rect, RectangleExt).size;
this.target = new Point().setX(part.point.x * size.x).setY(part.point.y * size.y);
}
private function get_currentImage():BitmapData { private function redraw():Void {
if (image == null) { throw "Unimplemented";
return null; }
}
return completed ? image.borderedImage : image.shadedImage; private function refresh():Void {
if (position != null && image != null) {
x = position.x - (image.borderedImage.width - size.x) / 2;
y = position.y - (image.borderedImage.height - size.y) / 2;
} }
}
public var size(default, null):Point; public function complete():Void {
position = target;
playerId = null;
completed = true;
refresh();
redraw();
}
public var playerId(default, set):Null<String>; public static function factory(part:Part):PartView {
return new SpritePartView(part);
private function set_playerId(value:Null<String>):Null<String> { }
if (playerId != value) {
playerId = value;
redraw();
}
return playerId;
}
public function new(part:Part) {
super();
this.id = part.id;
this.part = part;
this.size = cast(part.rect, RectangleExt).size;
this.target = new Point().setX(part.point.x * size.x).setY(part.point.y * size.y);
}
private function redraw():Void {
throw "Unimplemented";
}
private function refresh():Void {
if (position != null && image != null) {
x = position.x - (image.borderedImage.width - size.x) / 2;
y = position.y - (image.borderedImage.height - size.y) / 2;
}
}
public function complete():Void {
position = target;
playerId = null;
completed = true;
refresh();
redraw();
}
public static function factory(part:Part):PartView {
return new SpritePartView(part);
}
} }
class SpritePartView extends PartView { class SpritePartView extends PartView {
@:provide static var builder:IPartBuilder;
@:provide static var builder:IPartBuilder; override private function redraw():Void {
if (image == null) {
override private function redraw():Void { return;
if (image == null) {
return;
}
var image = currentImage;
graphics.clear();
graphics.beginBitmapFill(image, null, false, true);
graphics.drawRect(0, 0, image.width, image.height);
graphics.endFill();
if (playerId != null) {
var rect = cast(part.rect, RectangleExt).clone();
rect.x += (image.width - size.x) / 2;
rect.y += (image.height - size.y) / 2;
var path = builder.build(rect, part.bounds);
graphics.lineStyle(4, 0xffff00, 0.3);
path.draw(graphics);
}
} }
var image = currentImage;
graphics.clear();
graphics.beginBitmapFill(image, null, false, true);
graphics.drawRect(0, 0, image.width, image.height);
graphics.endFill();
if (playerId != null) {
var rect = cast(part.rect, RectangleExt).clone();
rect.x += (image.width - size.x) / 2;
rect.y += (image.height - size.y) / 2;
var path = builder.build(rect, part.bounds);
graphics.lineStyle(4, 0xffff00, 0.3);
path.draw(graphics);
}
}
} }
class BitmapPartView extends PartView { class BitmapPartView extends PartView {
private var bitmap:Bitmap; private var bitmap:Bitmap;
public function new(part) { public function new(part) {
super(part); super(part);
bitmap = new Bitmap(null, PixelSnapping.AUTO, true); bitmap = new Bitmap(null, PixelSnapping.AUTO, true);
addChild(bitmap); addChild(bitmap);
} }
override private function redraw():Void { override private function redraw():Void {
bitmap.bitmapData = currentImage; bitmap.bitmapData = currentImage;
} }
} }

View File

@@ -3,19 +3,18 @@ package ru.m.puzzlez.render;
import hw.view.form.LabelView; import hw.view.form.LabelView;
class ProgressView extends LabelView { class ProgressView extends LabelView {
public function new() {
super();
text = 'Loading...';
}
public function new() { public function setProgress(current:Int, total:Int):Void {
super(); text = 'Loading ${current}/${total}';
text = 'Loading...'; }
}
public function setProgress(current:Int, total:Int):Void { override private function set_text(value:String):String {
text = 'Loading ${current}/${total}'; var result = super.set_text(value);
} update();
return result;
override private function set_text(value:String):String { }
var result = super.set_text(value);
update();
return result;
}
} }

View File

@@ -22,232 +22,217 @@ import ru.m.puzzlez.settings.Settings;
import ru.m.puzzlez.wrap.PointExt; import ru.m.puzzlez.wrap.PointExt;
class Render extends SpriteView implements IRender { class Render extends SpriteView implements IRender {
public var actions(default, null):Signal<GameAction>;
public var scale(get, set):Float;
public var manager(default, null):RenderManager;
public var actions(default, null):Signal<GameAction>; @:provide static var settings:Settings;
public var scale(get, set):Float;
public var manager(default, null):RenderManager;
@:provide static var settings:Settings; private var playerId(get, never):String;
private var playerId(get, never):String; private function get_playerId():String {
// ToDo: network user
return "local";
}
private function get_playerId():String { private function get_scale():Float {
// ToDo: network user return tableView.scaleX;
return "local"; }
private function set_scale(value:Float):Float {
var result = tableView.scaleX = tableView.scaleY = value;
tableView.x = (width - state.preset.tableRect.width * value) / 2;
tableView.y = (height - state.preset.tableRect.height * value) / 2;
// setSize(table.width, table.height, 'table');
return result;
}
private var state:GameState;
private var image:BitmapData;
private var progress:ProgressView;
private var container:Sprite;
private var tableView:Sprite;
private var imageView:CompleteView;
private var parts:Map<Int, PartView>;
private var activePart:PartView;
private var activePoint:PointExt;
private var movePoint:FlashPoint;
public function new() {
super();
container = new Sprite();
content.addChild(container);
manager = new RenderManager(content, container);
manager.locked = settings.locked;
progress = new ProgressView();
actions = new Signal();
tableView = new Sprite();
tableView.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
imageView = new CompleteView();
tableView.addChild(imageView);
container.addChild(tableView);
parts = new Map();
}
public function onGameEvent(event:GameEvent):Void {
if (event.hasStart()) {
onStart(event.start.state);
} else if (event.hasChange()) {
var part:PartView = parts[event.change.partId];
part.playerId = event.change.playerId;
part.position = event.change.position;
switch event.change.location {
case PartLocation.IMAGE:
part.complete();
imageView.addChild(part);
imageView.redraw();
case _:
}
} else if (event.hasComplete()) {
AlertView.alert("Complete!");
}
}
private function onStart(state:GameState):Void {
clean();
this.state = state;
for (part in state.parts) {
var partView = PartView.factory(part);
parts.set(part.id, partView);
switch part.location {
case PartLocation.TABLE:
partView.position = part.position;
tableView.addChild(partView);
case PartLocation.IMAGE:
partView.complete();
imageView.addChild(partView);
case _:
}
} }
private function get_scale():Float { imageView.x = state.preset.imageRect.x;
return tableView.scaleX; imageView.y = state.preset.imageRect.y;
} imageView.preset = state.preset;
imageView.redraw();
progress.text = "Loading image";
content.addChild(progress.content);
ImageData.fromImageId(state.preset.image).resolve().then(onImageResolved);
toUpdate();
}
private function set_scale(value:Float):Float { private function onImageResolved(image:BitmapData):Void {
var result = tableView.scaleX = tableView.scaleY = value; this.image = RenderUtil.cropImage(image, state.preset.imageRect);
tableView.x = (width - state.preset.tableRect.width * value) / 2; var builder = new ImagePartBuilder(this.image);
tableView.y = (height - state.preset.tableRect.height * value) / 2; var i = 0;
//setSize(table.width, table.height, 'table'); builder.build(state.parts).then((result:Result) -> {
return result; parts[result.part.id].image = result.image;
} progress.setProgress(++i, state.parts.length);
if (i >= state.parts.length - 1) {
private var state:GameState; if (progress.content.parent != null) {
private var image:BitmapData; progress.content.parent.removeChild(progress.content);
private var progress:ProgressView;
private var container:Sprite;
private var tableView:Sprite;
private var imageView:CompleteView;
private var parts:Map<Int, PartView>;
private var activePart:PartView;
private var activePoint:PointExt;
private var movePoint:FlashPoint;
public function new() {
super();
container = new Sprite();
content.addChild(container);
manager = new RenderManager(content, container);
manager.locked = settings.locked;
progress = new ProgressView();
actions = new Signal();
tableView = new Sprite();
tableView.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
imageView = new CompleteView();
tableView.addChild(imageView);
container.addChild(tableView);
parts = new Map();
}
public function onGameEvent(event:GameEvent):Void {
if (event.hasStart()) {
onStart(event.start.state);
} else if (event.hasChange()) {
var part:PartView = parts[event.change.partId];
part.playerId = event.change.playerId;
part.position = event.change.position;
switch event.change.location {
case PartLocation.IMAGE:
part.complete();
imageView.addChild(part);
imageView.redraw();
case _:
}
} else if (event.hasComplete()) {
AlertView.alert("Complete!");
} }
}
private function onStart(state:GameState):Void {
clean();
this.state = state;
for (part in state.parts) {
var partView = PartView.factory(part);
parts.set(part.id, partView);
switch part.location {
case PartLocation.TABLE:
partView.position = part.position;
tableView.addChild(partView);
case PartLocation.IMAGE:
partView.complete();
imageView.addChild(partView);
case _:
}
}
imageView.x = state.preset.imageRect.x;
imageView.y = state.preset.imageRect.y;
imageView.preset = state.preset;
imageView.redraw(); imageView.redraw();
progress.text = "Loading image"; }
content.addChild(progress.content); });
ImageData.fromImageId(state.preset.image).resolve().then(onImageResolved); }
toUpdate();
}
private function onImageResolved(image:BitmapData):Void { override public function update():Void {
this.image = RenderUtil.cropImage(image, state.preset.imageRect); super.update();
var builder = new ImagePartBuilder(this.image); progress.x = (width - progress.width) / 2;
var i = 0; progress.y = (height - progress.height) / 2;
builder.build(state.parts).then((result:Result) -> { progress.update();
parts[result.part.id].image = result.image; if (state != null) {
progress.setProgress(++i, state.parts.length); scale = Math.min(width / state.preset.tableRect.width, height / state.preset.tableRect.height);
if (i >= state.parts.length - 1) { }
if (progress.content.parent != null) { }
progress.content.parent.removeChild(progress.content);
} override public function redraw():Void {
imageView.redraw(); switch settings.background {
} case Background.NONE:
super.redraw();
case Background.COLOR(color):
content.graphics.clear();
content.graphics.beginFill(color);
content.graphics.drawRect(0, 0, width, height);
content.graphics.endFill();
case Background.IMAGE(id):
ImageData.fromImageId(id).resolve().then(result -> {
content.graphics.clear();
content.graphics.beginBitmapFill(result);
content.graphics.drawRect(0, 0, width, height);
content.graphics.endFill();
}); });
} }
}
override public function update():Void { private function onMouseDown(event:MouseEvent):Void {
super.update(); var point:FlashPoint = new FlashPoint(event.stageX, event.stageY);
progress.x = (width - progress.width) / 2; var objects = tableView.getObjectsUnderPoint(point);
progress.y = (height - progress.height) / 2; objects.reverse();
progress.update(); var pointPart:PartView = null;
if (state != null) { for (object in objects) {
scale = Math.min(width / state.preset.tableRect.width, height / state.preset.tableRect.height); if (Std.is(object, PartView)) {
var part:PartView = cast object;
var partPoint = part.globalToLocal(point);
var color = part.image.shadedImage.getPixel(Std.int(partPoint.x), Std.int(partPoint.y));
if (color > 0) {
pointPart = part;
break;
} }
}
} }
if (pointPart != null) {
event.stopImmediatePropagation();
if (event.ctrlKey) {
save(pointPart);
return;
}
if (pointPart.completed) {
return;
}
activePart = pointPart;
tableView.setChildIndex(activePart, tableView.numChildren - 1);
activePoint = tableView.globalToLocal(point);
tableView.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
tableView.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
actions.emit(new GameAction().setAction(Action.TAKE).setPlayerId(playerId).setPartId(activePart.id));
}
}
override public function redraw():Void { private function onMouseMove(event:MouseEvent):Void {
switch settings.background { var newPoint:PointExt = tableView.globalToLocal(new FlashPoint(event.stageX, event.stageY));
case Background.NONE: var partPosition = cast(activePart.position, PointExt).add(newPoint).subtract(activePoint);
super.redraw(); actions.emit(new GameAction().setAction(Action.MOVE).setPlayerId(playerId).setPartId(activePart.id).setPosition(partPosition));
case Background.COLOR(color): activePoint = newPoint;
content.graphics.clear(); }
content.graphics.beginFill(color);
content.graphics.drawRect(0, 0, width, height);
content.graphics.endFill();
case Background.IMAGE(id):
ImageData.fromImageId(id).resolve().then(result -> {
content.graphics.clear();
content.graphics.beginBitmapFill(result);
content.graphics.drawRect(0, 0, width, height);
content.graphics.endFill();
});
}
}
private function onMouseDown(event:MouseEvent):Void { private function onMouseUp(event:MouseEvent):Void {
var point:FlashPoint = new FlashPoint(event.stageX, event.stageY); var newPoint:PointExt = tableView.globalToLocal(new FlashPoint(event.stageX, event.stageY));
var objects = tableView.getObjectsUnderPoint(point); var partPosition = cast(activePart.position, PointExt).add(newPoint).subtract(activePoint);
objects.reverse(); actions.emit(new GameAction().setAction(Action.PUT).setPlayerId(playerId).setPartId(activePart.id).setPosition(partPosition));
var pointPart:PartView = null; tableView.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
for (object in objects) { tableView.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
if (Std.is(object, PartView)) { activePart = null;
var part:PartView = cast object; activePoint = null;
var partPoint = part.globalToLocal(point); }
var color = part.image.shadedImage.getPixel(Std.int(partPoint.x), Std.int(partPoint.y));
if (color > 0) {
pointPart = part;
break;
}
}
}
if (pointPart != null) {
event.stopImmediatePropagation();
if (event.ctrlKey) {
save(pointPart);
return;
}
if (pointPart.completed) {
return;
}
activePart = pointPart;
tableView.setChildIndex(activePart, tableView.numChildren - 1);
activePoint = tableView.globalToLocal(point);
tableView.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
tableView.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
actions.emit(new GameAction()
.setAction(Action.TAKE)
.setPlayerId(playerId)
.setPartId(activePart.id)
);
}
}
private function onMouseMove(event:MouseEvent):Void { private function save(part:PartView):Void {
var newPoint:PointExt = tableView.globalToLocal(new FlashPoint(event.stageX, event.stageY)); var file = new FileReference();
var partPosition = cast(activePart.position, PointExt).add(newPoint).subtract(activePoint); var image = part.image.shadedImage;
actions.emit(new GameAction() var data = new ByteArray();
.setAction(Action.MOVE) image.encode(new Rectangle(0, 0, image.width, image.height), new PNGEncoderOptions(), data);
.setPlayerId(playerId) file.save(data, "icon.png");
.setPartId(activePart.id) }
.setPosition(partPosition)
);
activePoint = newPoint;
}
private function onMouseUp(event:MouseEvent):Void { public function clean() {
var newPoint:PointExt = tableView.globalToLocal(new FlashPoint(event.stageX, event.stageY)); for (partView in parts) {
var partPosition = cast(activePart.position, PointExt).add(newPoint).subtract(activePoint); if (partView.parent != null) {
actions.emit(new GameAction() partView.parent.removeChild(partView);
.setAction(Action.PUT) }
.setPlayerId(playerId)
.setPartId(activePart.id)
.setPosition(partPosition)
);
tableView.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
tableView.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
activePart = null;
activePoint = null;
}
private function save(part:PartView):Void {
var file = new FileReference();
var image = part.image.shadedImage;
var data = new ByteArray();
image.encode(new Rectangle(0, 0, image.width, image.height), new PNGEncoderOptions(), data);
file.save(data, "icon.png");
}
public function clean() {
for (partView in parts) {
if (partView.parent != null) {
partView.parent.removeChild(partView);
}
}
parts = new Map();
imageView.clean();
} }
parts = new Map();
imageView.clean();
}
} }

View File

@@ -7,62 +7,61 @@ import flash.events.MouseEvent;
import flash.geom.Point; import flash.geom.Point;
class RenderManager { class RenderManager {
public var locked(default, default):Bool;
public var locked(default, default):Bool; private var content:DisplayObject;
private var container:DisplayObject;
private var movePoint:Point;
private var gesture:GestureManager;
private var content:DisplayObject; public function new(content:DisplayObject, container:DisplayObject) {
private var container:DisplayObject; this.content = content;
private var movePoint:Point; this.container = container;
private var gesture:GestureManager; content.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
content.addEventListener(ZoomGestureEvent.GESTURE_ZOOM, onGestureZoom);
gesture = new GestureManager(content);
}
public function new(content:DisplayObject, container:DisplayObject) { private function onGestureZoom(event:ZoomGestureEvent):Void {
this.content = content; if (locked) {
this.container = container; return;
content.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
content.addEventListener(ZoomGestureEvent.GESTURE_ZOOM, onGestureZoom);
gesture = new GestureManager(content);
} }
container.scaleX = container.scaleY += event.zoom;
}
private function onGestureZoom(event:ZoomGestureEvent):Void { private function onMouseDown(event:MouseEvent):Void {
if (locked) { if (locked) {
return; return;
}
container.scaleX = container.scaleY += event.zoom;
} }
movePoint = new Point(event.stageX, event.stageY);
content.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
content.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
private function onMouseDown(event:MouseEvent):Void { private function onMouseMove(event:MouseEvent):Void {
if (locked) { var newPoint = new Point(event.stageX, event.stageY);
return; var diff = newPoint.subtract(movePoint);
} container.x += diff.x;
movePoint = new Point(event.stageX, event.stageY); container.y += diff.y;
content.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); movePoint = newPoint;
content.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); }
}
private function onMouseMove(event:MouseEvent):Void { private function onMouseUp(event:MouseEvent):Void {
var newPoint = new Point(event.stageX, event.stageY); content.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
var diff = newPoint.subtract(movePoint); content.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
container.x += diff.x; movePoint = null;
container.y += diff.y; }
movePoint = newPoint;
}
private function onMouseUp(event:MouseEvent):Void { public function reset():Void {
content.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); container.x = 0;
content.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); container.y = 0;
movePoint = null; }
}
public function reset():Void { public function dispose():Void {
container.x = 0; if (gesture != null) {
container.y = 0; gesture.dispose();
} gesture = null;
public function dispose():Void {
if (gesture != null) {
gesture.dispose();
gesture = null;
}
content.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
} }
content.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}
} }

View File

@@ -13,115 +13,113 @@ import ru.m.puzzlez.render.part.PartMask;
import ru.m.puzzlez.wrap.RectangleExt; import ru.m.puzzlez.wrap.RectangleExt;
typedef DrawSetting = { typedef DrawSetting = {
var offset:Point; var offset:Point;
var color:Color; var color:Color;
var opacity:Float; var opacity:Float;
} }
typedef PartImage = { typedef PartImage = {
var borderedImage:BitmapData; var borderedImage:BitmapData;
var shadedImage:BitmapData; var shadedImage:BitmapData;
} }
class RenderUtil { class RenderUtil {
public static var shadowSettings(default, null):Array<DrawSetting> = [ public static var shadowSettings(default, null):Array<DrawSetting> = [
{offset: new Point().setX(-2).setY(-2), color: 0x000000, opacity: 0.4}, {offset: new Point().setX(-2).setY(-2), color: 0x000000, opacity: 0.4},
{offset: new Point().setX(2).setY(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().setX(-1).setY(-1), color: 0x555555, opacity: 0.4}, {offset: new Point().setX(-1).setY(-1), color: 0x555555, opacity: 0.4},
{offset: new Point().setX(1).setY(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;
public static function cropImage(source:BitmapData, rect:Rectangle):BitmapData { public static function cropImage(source:BitmapData, rect:Rectangle):BitmapData {
var image = new BitmapData(Std.int(rect.width), Std.int(rect.height)); var image = new BitmapData(Std.int(rect.width), Std.int(rect.height));
var matrix = new Matrix(); var matrix = new Matrix();
var scale = Math.max(rect.width / source.width, rect.height / source.height); var scale = Math.max(rect.width / source.width, rect.height / source.height);
matrix.scale(scale, scale); matrix.scale(scale, scale);
matrix.translate((rect.width - source.width * scale) / 2, (rect.height - source.height * scale) / 2); matrix.translate((rect.width - source.width * scale) / 2, (rect.height - source.height * scale) / 2);
image.draw(source, matrix); image.draw(source, matrix);
return image; return image;
}
private static function buildPartGeometry(part:Part):{rect:Rectangle, drawRect:Rectangle} {
var source = new Rectangle().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;
rect.x -= offset;
rect.y -= offset;
rect.width += offset * 2;
rect.height += offset * 2;
var drawRect = cast(source, RectangleExt).clone();
drawRect.x = offset;
drawRect.y = offset;
return {rect: rect, drawRect: drawRect};
}
private static function drawShadow(source:BitmapData, path:DrawPath, values:Array<DrawSetting>):BitmapData {
var canvas = new Shape();
canvas.cacheAsBitmap = true;
for (value in values) {
canvas.graphics.beginFill(value.color, value.opacity);
path.move(value.offset.x, value.offset.y).draw(canvas.graphics);
canvas.graphics.endFill();
} }
canvas.graphics.beginBitmapFill(source, null, false, true);
canvas.graphics.drawRect(0, 0, source.width, source.height);
canvas.graphics.endFill();
var image = new BitmapData(source.width, source.height, true, 0x00000000);
image.draw(canvas, null, null, null, null, true);
return image;
}
private static function buildPartGeometry(part:Part):{rect:Rectangle, drawRect:Rectangle} { private static function drawBorder(source:BitmapData, path:DrawPath, values:Array<DrawSetting>):BitmapData {
var source = new Rectangle() var canvas = new Shape();
.setX(part.rect.width * part.point.x) canvas.cacheAsBitmap = true;
.setY(part.rect.height * part.point.y) for (value in values) {
.setWidth(part.rect.width) canvas.graphics.lineStyle(2, value.color, value.opacity);
.setHeight(part.rect.height); path.move(value.offset.x, value.offset.y).draw(canvas.graphics);
var rect = cast(source, RectangleExt).clone(); canvas.graphics.lineStyle();
var offset = rect.width / 4 + rect.width * 0.05;
rect.x -= offset;
rect.y -= offset;
rect.width += offset * 2;
rect.height += offset * 2;
var drawRect = cast(source, RectangleExt).clone();
drawRect.x = offset;
drawRect.y = offset ;
return {rect:rect, drawRect:drawRect};
} }
canvas.graphics.beginBitmapFill(source, null, false, true);
canvas.graphics.drawRect(0, 0, source.width, source.height);
canvas.graphics.endFill();
var image = new BitmapData(source.width, source.height, true, 0x00000000);
image.draw(canvas, null, null, null, null, true);
return image;
}
private static function drawShadow(source:BitmapData, path:DrawPath, values:Array<DrawSetting>):BitmapData { public static function cropImagePart(source:BitmapData, part:Part, completed:Bool = false):PartImage {
var canvas = new Shape(); var geometry = buildPartGeometry(part);
canvas.cacheAsBitmap = true; var path = builder.build(geometry.drawRect, part.bounds);
for (value in values) { var canvas:Shape = new Shape();
canvas.graphics.beginFill(value.color, value.opacity); canvas.cacheAsBitmap = true;
path.move(value.offset.x, value.offset.y).draw(canvas.graphics); canvas.mask = new PartMask(path);
canvas.graphics.endFill(); var matrix = new Matrix();
} matrix.translate(-geometry.rect.x, -geometry.rect.y);
canvas.graphics.beginBitmapFill(source, null, false, true); canvas.graphics.beginBitmapFill(source, matrix, false, true);
canvas.graphics.drawRect(0, 0, source.width, source.height); canvas.graphics.drawRect(0, 0, geometry.rect.width, geometry.rect.height);
canvas.graphics.endFill(); canvas.graphics.endFill();
var image = new BitmapData(source.width, source.height, true, 0x00000000); var image = new BitmapData(Std.int(geometry.rect.width), Std.int(geometry.rect.height), true, 0x00000000);
image.draw(canvas, null, null, null, null, true); image.draw(canvas, null, null, null, null, true);
return image; var shadedImage = drawShadow(image, path, shadowSettings);
} // var borderedImage = drawBorder(image, path, borderSettings);
return {borderedImage: image, shadedImage: shadedImage};
}
private static function drawBorder(source:BitmapData, path:DrawPath, values:Array<DrawSetting>):BitmapData { public static function containRectangle(source:Rectangle, target:Rectangle):Rectangle {
var canvas = new Shape(); var s = Math.min(1, Math.min(target.width / source.width, target.height / source.height));
canvas.cacheAsBitmap = true; var width = source.width * s;
for (value in values) { var height = source.height * s;
canvas.graphics.lineStyle(2, value.color, value.opacity); return new Rectangle().setX((target.width - width) / 2)
path.move(value.offset.x, value.offset.y).draw(canvas.graphics); .setY((target.height - height) / 2)
canvas.graphics.lineStyle(); .setWidth(width)
} .setHeight(height);
canvas.graphics.beginBitmapFill(source, null, false, true); }
canvas.graphics.drawRect(0, 0, source.width, source.height);
canvas.graphics.endFill();
var image = new BitmapData(source.width, source.height, true, 0x00000000);
image.draw(canvas, null, null, null, null, true);
return image;
}
public static function cropImagePart(source:BitmapData, part:Part, completed:Bool = false):PartImage {
var geometry = buildPartGeometry(part);
var path = builder.build(geometry.drawRect, part.bounds);
var canvas:Shape = new Shape();
canvas.cacheAsBitmap = true;
canvas.mask = new PartMask(path);
var matrix = new Matrix();
matrix.translate(-geometry.rect.x, -geometry.rect.y);
canvas.graphics.beginBitmapFill(source, matrix, false, true);
canvas.graphics.drawRect(0, 0, geometry.rect.width, geometry.rect.height);
canvas.graphics.endFill();
var image = new BitmapData(Std.int(geometry.rect.width), Std.int(geometry.rect.height), true, 0x00000000);
image.draw(canvas, null, null, null, null, true);
var shadedImage = drawShadow(image, path, shadowSettings);
//var borderedImage = drawBorder(image, path, borderSettings);
return {borderedImage: image, shadedImage: shadedImage};
}
public static function containRectangle(source:Rectangle, target:Rectangle):Rectangle {
var s = Math.min(1, Math.min(target.width / source.width, target.height / source.height));
var width = source.width * s;
var height = source.height * s;
return new Rectangle()
.setX((target.width - width) / 2)
.setY((target.height - height) / 2)
.setWidth(width)
.setHeight(height);
}
} }

View File

@@ -8,28 +8,25 @@ 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 {
public function new() {}
public function new() { private function createAngle(path:DrawPath, x:Float, y:Float, first:Bool = false):Void {
} path.commands.push(first ? MOVE_TO(x, y) : LINE_TO(x, y));
}
private function createAngle(path:DrawPath, x:Float, y:Float, first: Bool = false):Void { private function createBound(path:DrawPath, fromX:Float, fromY:Float, toX:Float, toY:Float, bound:PartBound):Void {}
path.commands.push(first ? MOVE_TO(x, y) : LINE_TO(x, y));
}
private function createBound(path:DrawPath, fromX:Float, fromY:Float, toX:Float, toY:Float, bound:PartBound):Void { public function build(rect:RectangleExt, bounds:PartBounds):DrawPath {
} var path = new DrawPath();
createAngle(path, rect.left, rect.top, true);
public function build(rect:RectangleExt, bounds:PartBounds):DrawPath { createBound(path, rect.left, rect.top, rect.right, rect.top, bounds.top);
var path = new DrawPath(); createAngle(path, rect.right, rect.top);
createAngle(path, rect.left, rect.top, true); createBound(path, rect.right, rect.top, rect.right, rect.bottom, bounds.right);
createBound(path, rect.left, rect.top, rect.right, rect.top, bounds.top); createAngle(path, rect.right, rect.bottom);
createAngle(path, rect.right, rect.top); createBound(path, rect.right, rect.bottom, rect.left, rect.bottom, bounds.bottom);
createBound(path, rect.right, rect.top, rect.right, rect.bottom, bounds.right); createAngle(path, rect.left, rect.bottom);
createAngle(path, rect.right, rect.bottom); createBound(path, rect.left, rect.bottom, rect.left, rect.top, bounds.left);
createBound(path, rect.right, rect.bottom, rect.left, rect.bottom, bounds.bottom); createAngle(path, rect.left, rect.top);
createAngle(path, rect.left, rect.bottom); return path;
createBound(path, rect.left, rect.bottom, rect.left, rect.top, bounds.left); }
createAngle(path, rect.left, rect.top);
return path;
}
} }

View File

@@ -5,42 +5,55 @@ import ru.m.puzzlez.proto.game.BoundType;
import ru.m.puzzlez.proto.game.PartBound; import ru.m.puzzlez.proto.game.PartBound;
class ClassicPartBuilder extends BasePartBuilder { class ClassicPartBuilder extends BasePartBuilder {
override private function createBound(path:DrawPath, fromX:Float, fromY:Float, toX:Float, toY:Float, bound:PartBound):Void {
override private function createBound(path:DrawPath, fromX:Float, fromY:Float, toX:Float, toY:Float, bound:PartBound):Void { var dx = toX == fromX ? 0 : (toX - fromX) / Math.abs(toX - fromX);
var dx = toX == fromX ? 0 : (toX - fromX) / Math.abs(toX - fromX); var dy = toY == fromY ? 0 : (toY - fromY) / Math.abs(toY - fromY);
var dy = toY == fromY ? 0 : (toY - fromY) / Math.abs(toY - fromY); var boundLength = Math.max(Math.abs(toX - fromX), Math.abs(toY - fromY));
var boundLength = Math.max(Math.abs(toX - fromX), Math.abs(toY - fromY)); 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 BoundType.IN: spikeWidth;
case BoundType.IN: spikeWidth; case BoundType.OUT: -spikeWidth;
case BoundType.OUT: -spikeWidth; case _: 0;
case _: 0; };
}; var spikeSideOffset = switch bound.side {
var spikeSideOffset = switch bound.side { case BoundType.IN: boundLength * -0.04;
case BoundType.IN: boundLength * -0.04; case BoundType.OUT: boundLength * 0.04;
case BoundType.OUT: boundLength * 0.04; case _: 0;
case _: 0;
}
switch bound.spike {
case BoundType.IN | BoundType.OUT:
path.commands.push(LINE_TO(
fromX + spikeOffset * dx + spikeSideOffset * dy,
fromY + spikeOffset * dy + spikeSideOffset * dx
));
path.commands.push(CURVE_TO(
fromX + (spikeOffset * 0.7) * dx - spikeDepth * dy + spikeSideOffset * dy,
fromY + spikeDepth * dx + (spikeOffset * 0.7) * dy + spikeSideOffset * dx,
fromX + (spikeOffset + spikeWidth / 2) * dx - (spikeDepth * 1.1) * dy + spikeSideOffset * dy,
fromY + (spikeDepth * 1.1) * dx + (spikeOffset + spikeWidth / 2) * dy + spikeSideOffset * dx
));
path.commands.push(CURVE_TO(
fromX + (spikeOffset + spikeWidth + spikeOffset * 0.3) * dx - spikeDepth * dy + spikeSideOffset * dy,
fromY + spikeDepth * dx + (spikeOffset + spikeWidth + spikeOffset * 0.3) * dy + spikeSideOffset * dx,
fromX + (spikeOffset + spikeWidth) * dx + spikeSideOffset * dy,
fromY + (spikeOffset + spikeWidth) * dy + spikeSideOffset * dx
));
case _:
}
} }
switch bound.spike {
case BoundType.IN | BoundType.OUT:
path.commands.push(LINE_TO(fromX + spikeOffset * dx + spikeSideOffset * dy, fromY + spikeOffset * dy + spikeSideOffset * dx));
path.commands.push(CURVE_TO(fromX
+ (spikeOffset * 0.7) * dx
- spikeDepth * dy
+ spikeSideOffset * dy,
fromY
+ spikeDepth * dx
+ (spikeOffset * 0.7) * dy
+ spikeSideOffset * dx,
fromX
+ (spikeOffset + spikeWidth / 2) * dx
- (spikeDepth * 1.1) * dy
+ spikeSideOffset * dy,
fromY
+ (spikeDepth * 1.1) * dx
+ (spikeOffset + spikeWidth / 2) * dy
+ spikeSideOffset * dx));
path.commands.push(CURVE_TO(fromX
+ (spikeOffset + spikeWidth + spikeOffset * 0.3) * dx
- spikeDepth * dy
+ spikeSideOffset * dy,
fromY
+ spikeDepth * dx
+ (spikeOffset + spikeWidth + spikeOffset * 0.3) * dy
+ spikeSideOffset * dx,
fromX
+ (spikeOffset + spikeWidth) * dx
+ spikeSideOffset * dy, fromY
+ (spikeOffset + spikeWidth) * dy
+ spikeSideOffset * dx));
case _:
}
}
} }

View File

@@ -5,5 +5,5 @@ import ru.m.puzzlez.proto.game.PartBounds;
import ru.m.puzzlez.wrap.RectangleExt; import ru.m.puzzlez.wrap.RectangleExt;
@:provide(ClassicPartBuilder) interface IPartBuilder { @:provide(ClassicPartBuilder) interface IPartBuilder {
public function build(rect:RectangleExt, bound:PartBounds):DrawPath; public function build(rect:RectangleExt, bound:PartBounds):DrawPath;
} }

View File

@@ -4,11 +4,10 @@ import flash.display.Shape;
import ru.m.draw.DrawPath; import ru.m.draw.DrawPath;
class PartMask extends Shape { class PartMask extends Shape {
public function new(path:DrawPath) {
public function new(path:DrawPath) { super();
super(); graphics.beginFill(0xff00ff, 1);
graphics.beginFill(0xff00ff, 1); path.draw(graphics);
path.draw(graphics); graphics.endFill();
graphics.endFill(); }
}
} }

View File

@@ -5,37 +5,24 @@ import ru.m.puzzlez.proto.game.BoundType;
import ru.m.puzzlez.proto.game.PartBound; import ru.m.puzzlez.proto.game.PartBound;
class SquarePartBuilder extends BasePartBuilder { class SquarePartBuilder extends BasePartBuilder {
override private function createBound(path:DrawPath, fromX:Float, fromY:Float, toX:Float, toY:Float, bound:PartBound):Void {
override private function createBound(path:DrawPath, fromX:Float, fromY:Float, toX:Float, toY:Float, bound:PartBound):Void { var dx = toX == fromX ? 0 : (toX - fromX) / Math.abs(toX - fromX);
var dx = toX == fromX ? 0 : (toX - fromX) / Math.abs(toX - fromX); var dy = toY == fromY ? 0 : (toY - fromY) / Math.abs(toY - fromY);
var dy = toY == fromY ? 0 : (toY - fromY) / Math.abs(toY - fromY); var boundLength = Math.max(Math.abs(toX - fromX), Math.abs(toY - fromY));
var boundLength = Math.max(Math.abs(toX - fromX), Math.abs(toY - fromY)); 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 BoundType.NONE: 0;
case BoundType.NONE: 0; case BoundType.IN: spikeWidth;
case BoundType.IN: spikeWidth; case BoundType.OUT: -spikeWidth;
case BoundType.OUT: -spikeWidth;
}
switch bound.spike {
case BoundType.NONE:
case BoundType.IN | BoundType.OUT:
path.commands.push(LINE_TO(
fromX + spikeOffset * dx,
fromY + spikeOffset * dy
))
path.commands.push(LINE_TO(
fromX + spikeOffset * dx - spikeDepth * dy,
fromY + spikeDepth * dx + spikeOffset * dy
));
path.commands.push(LINE_TO(
fromX + (spikeOffset + spikeWidth) * dx - spikeDepth * dy,
fromY + spikeDepth * dx + (spikeOffset + spikeWidth) * dy
));
path.commands.push(LINE_TO(
fromX + (spikeOffset + spikeWidth) * dx,
fromY + (spikeOffset + spikeWidth) * dy
));
}
} }
switch bound.spike {
case BoundType.NONE:
case BoundType.IN | BoundType.OUT:
path.commands.push(LINE_TO(fromX + spikeOffset * dx,
fromY + spikeOffset * dy)) path.commands.push(LINE_TO(fromX + spikeOffset * dx - spikeDepth * dy, fromY + spikeDepth * dx + spikeOffset * dy));
path.commands.push(LINE_TO(fromX + (spikeOffset + spikeWidth) * dx - spikeDepth * dy, fromY + spikeDepth * dx + (spikeOffset + spikeWidth) * dy));
path.commands.push(LINE_TO(fromX + (spikeOffset + spikeWidth) * dx, fromY + (spikeOffset + spikeWidth) * dy));
}
}
} }

View File

@@ -4,33 +4,33 @@ import hw.storage.SharedObjectStorage;
import ru.m.puzzlez.render.Background; import ru.m.puzzlez.render.Background;
@:provide class Settings extends SharedObjectStorage { @:provide class Settings extends SharedObjectStorage {
private inline static var VERSION = 2; private inline static var VERSION = 2;
private inline static var BACKGROUND_KEY = "background"; private inline static var BACKGROUND_KEY = "background";
private inline static var LOCKED_KEY = "locked"; private inline static var LOCKED_KEY = "locked";
public var background(get, set):Background; public var background(get, set):Background;
private inline function get_background():Background { private inline function get_background():Background {
return exists(BACKGROUND_KEY) ? read(BACKGROUND_KEY) : NONE; return exists(BACKGROUND_KEY) ? read(BACKGROUND_KEY) : NONE;
} }
private inline function set_background(value:Background):Background { private inline function set_background(value:Background):Background {
write(BACKGROUND_KEY, value); write(BACKGROUND_KEY, value);
return value; return value;
} }
public var locked(get, set):Bool; public var locked(get, set):Bool;
private inline function get_locked():Bool { private inline function get_locked():Bool {
return exists(LOCKED_KEY) ? read(LOCKED_KEY) : false; return exists(LOCKED_KEY) ? read(LOCKED_KEY) : false;
} }
private inline function set_locked(value:Bool):Bool { private inline function set_locked(value:Bool):Bool {
write(LOCKED_KEY, value); write(LOCKED_KEY, value);
return value; return value;
} }
public function new() { public function new() {
super('setting/${VERSION}'); super('setting/${VERSION}');
} }
} }

View File

@@ -9,34 +9,36 @@ import ru.m.puzzlez.core.ImageValue;
import ru.m.puzzlez.proto.game.ImageId; import ru.m.puzzlez.proto.game.ImageId;
class AssetImageSource implements ImageSource { class AssetImageSource implements ImageSource {
public var id(default, never):String = "asset"; public var id(default, never):String = "asset";
private var _data:Array<ImageId>; private var _data:Array<ImageId>;
private var data(get, null):Array<ImageId>; private var data(get, null):Array<ImageId>;
private function get_data():Array<ImageId> { private function get_data():Array<ImageId> {
if (_data == null) { if (_data == null) {
_data = resolveData(); _data = resolveData();
}
return _data;
} }
return _data;
}
public function new() { public function new() {}
}
private function resolveData():Array<ImageId> { private function resolveData():Array<ImageId> {
return [for (name in Assets.list(AssetType.IMAGE).filter((name:String) -> name.substr(0, 15) == "resources/image")) new ImageId().setSource(id).setId(name)]; return [
} for (name in Assets.list(AssetType.IMAGE).filter((name:String) -> name.substr(0, 15) == "resources/image"))
new ImageId().setSource(id).setId(name)
];
}
public function getPage(page:Page):Promise<DataPage<ImageId>> { public function getPage(page:Page):Promise<DataPage<ImageId>> {
return Promise.promise({ return Promise.promise({
page: page, page: page,
data: data.slice(page.index * page.count, page.count), data: data.slice(page.index * page.count, page.count),
total: data.length, total: data.length,
}); });
} }
public function load(id:String, thumb:Bool = false):Promise<ImageValue> { public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
return Promise.promise(ImageValue.BITMAP(Assets.getBitmapData(id))); return Promise.promise(ImageValue.BITMAP(Assets.getBitmapData(id)));
} }
} }

View File

@@ -8,24 +8,23 @@ import ru.m.puzzlez.proto.game.ImageId;
import ru.m.puzzlez.storage.FileStorage; import ru.m.puzzlez.storage.FileStorage;
class FileImageSource implements ImageSource { class FileImageSource implements ImageSource {
public var id(default, never):String = "file"; public var id(default, never):String = "file";
@:provide private var storage:FileStorage; @:provide private var storage:FileStorage;
public function new() { public function new() {}
}
public function getPage(page:Page):Promise<DataPage<ImageId>> { public function getPage(page:Page):Promise<DataPage<ImageId>> {
return storage.getIndexPage(page).then((response:DataPage<String>) -> { return storage.getIndexPage(page).then((response:DataPage<String>) -> {
return { return {
page: response.page, page: response.page,
data: response.data.map(key -> new ImageId().setSource(id).setId(key)), data: response.data.map(key -> new ImageId().setSource(id).setId(key)),
total: response.total, total: response.total,
} }
}); });
} }
public function load(id:String, thumb:Bool = false):Promise<ImageValue> { public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
return storage.get(id).then(bytes -> ImageValue.BYTES(bytes)); return storage.get(id).then(bytes -> ImageValue.BYTES(bytes));
} }
} }

View File

@@ -1,53 +1,53 @@
package ru.m.puzzlez.source; package ru.m.puzzlez.source;
import promhx.Promise; import promhx.Promise;
import ru.m.api.PixabayApi;
import ru.m.data.DataSource; import ru.m.data.DataSource;
import ru.m.pixabay.PixabayApi;
import ru.m.puzzlez.core.ImageSource; import ru.m.puzzlez.core.ImageSource;
import ru.m.puzzlez.core.ImageValue; import ru.m.puzzlez.core.ImageValue;
import ru.m.puzzlez.proto.game.ImageId; import ru.m.puzzlez.proto.game.ImageId;
class PixabayImageSource implements ImageSource { class PixabayImageSource implements ImageSource {
public var id(default, never):String = "pixabay"; public var id(default, never):String = "pixabay";
private var api:PixabayApi; private var api:PixabayApi;
private static var imageUrlsCache:Map<String, String> = new Map();
public function new() { private static var imageUrlsCache:Map<String, String> = new Map();
var key:String = CompilationOption.get("PIXABAY_KEY");
api = new PixabayApi(key); public function new() {
var key:String = CompilationOption.get("PIXABAY_KEY");
api = new PixabayApi(key);
}
public function getPage(page:Page):Promise<DataPage<ImageId>> {
return this.api.getPage(page.index + 1, page.count, page.filter.get("category")).then((response:PixabayResponse) -> {
var data:Array<ImageId> = [];
for (hit in response.hits) {
imageUrlsCache.set('${hit.id}', hit.largeImageURL);
data.push(new ImageId().setSource(id).setId(Std.string(hit.id)));
}
return {
page: page,
data: data,
total: response.totalHits,
}
});
}
public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
var imageId = Std.parseInt(id);
var key = id;
if (thumb) {
key = '${key}:thumb';
} }
if (imageUrlsCache.exists(key)) {
public function getPage(page:Page):Promise<DataPage<ImageId>> { return Promise.promise(ImageValue.URL(imageUrlsCache.get(key)));
return this.api.getPage(page.index + 1, page.count, page.filter.get("category")) } else {
.then((response:PixabayResponse) -> { return api.get(imageId).then((data:PixabayImage) -> {
var data:Array<ImageId> = []; var url = thumb ? data.previewURL : data.largeImageURL;
for (hit in response.hits) { imageUrlsCache.set(key, url);
imageUrlsCache.set('${hit.id}', hit.largeImageURL); return ImageValue.URL(url);
data.push(new ImageId().setSource(id).setId(Std.string(hit.id))); });
}
return {
page: page,
data: data,
total: response.totalHits,
}
});
}
public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
var imageId = Std.parseInt(id);
var key = id;
if (thumb) {
key = '${key}:thumb';
}
if (imageUrlsCache.exists(key)) {
return Promise.promise(ImageValue.URL(imageUrlsCache.get(key)));
} else {
return api.get(imageId).then((data:PixabayImage) -> {
var url = thumb ? data.previewURL : data.largeImageURL;
imageUrlsCache.set(key, url);
return ImageValue.URL(url);
});
}
} }
}
} }

View File

@@ -7,76 +7,75 @@ import ru.m.puzzlez.core.ImageSource;
import ru.m.puzzlez.core.ImageValue; import ru.m.puzzlez.core.ImageValue;
import ru.m.puzzlez.proto.game.ImageId; import ru.m.puzzlez.proto.game.ImageId;
typedef Resolver = (id:String, thumb:Bool) -> Promise<String>;
typedef Resolver = (id:String, thumb: Bool) -> Promise<String>;
class ImageResolver { class ImageResolver {
private var cache:Map<String, String>;
private var resolver:Resolver;
private var cache: Map<String, String>; public function new(resolver:Resolver) {
private var resolver:Resolver; this.cache = new Map();
this.resolver = resolver;
}
public function new(resolver:Resolver) { private function buildKey(id:String, thumb:Bool = false):String {
this.cache = new Map(); var key = id;
this.resolver = resolver; if (thumb) {
key = '${key}:thumb';
} }
return key;
}
private function buildKey(id:String, thumb:Bool = false):String { public function setValue(id:String, url:String, thumb:Bool = false):Void {
var key = id; var key = buildKey(id, thumb);
if (thumb) { cache.set(key, url);
key = '${key}:thumb'; }
}
return key;
}
public function setValue(id:String, url:String):Void { public function resolve(id:String, thumb:Bool = false) {
cache.set(id, url); var key = buildKey(id, thumb);
} if (cache.exists(key)) {
return Promise.promise(ImageValue.URL(cache.get(key)));
public function resolve(id:String, thumb:Bool = false) { } else {
var key = buildKey(id, thumb); return resolver(id, thumb).then(url -> {
if (cache.exists(key)) { cache.set(key, url);
return Promise.promise(ImageValue.URL(cache.get(key))); return ImageValue.URL(url);
} else { });
return resolver(id, thumb).then(url -> {
cache.set(key, url);
return ImageValue.URL(url);
});
}
} }
}
} }
class UnsplashImageSource implements ImageSource { class UnsplashImageSource implements ImageSource {
public var id(default, never):String = "unsplash"; public var id(default, never):String = "unsplash";
private var api:UnsplashApi; private var api:UnsplashApi;
private static var resolver:ImageResolver;
public function new() { private static var resolver:ImageResolver;
var key:String = CompilationOption.get("UNSPLASH_KEY");
api = new UnsplashApi(key);
resolver = new ImageResolver((imageId, thumb) -> {
return api.get(imageId).then(data -> thumb ? data.urls.thumb : data.urls.regular);
});
}
public function getPage(page:Page):Promise<DataPage<ImageId>> { public function new() {
return this.api.getPage(page.index + 1, page.count, page.filter.get("category")) var key:String = CompilationOption.get("UNSPLASH_KEY");
.then((response:UnsplashResponse) -> { api = new UnsplashApi(key);
var data:Array<ImageId> = []; resolver = new ImageResolver((imageId, thumb) -> {
for (image in response.results) { return api.get(imageId).then(data -> thumb ? data.urls.small : data.urls.regular);
resolver.setValue(image.id, image.urls.regular); });
data.push(new ImageId().setSource(id).setId(image.id)); }
}
return {
page: page,
data: data,
total: response.total,
}
});
}
public function load(id:String, thumb:Bool = false):Promise<ImageValue> { public function getPage(page:Page):Promise<DataPage<ImageId>> {
return resolver.resolve(id, thumb); return this.api.getPage(page.index + 1, page.count, page.filter.get("category")).then((response:UnsplashResponse) -> {
} var data:Array<ImageId> = [];
for (image in response.results) {
resolver.setValue(image.id, image.urls.small, true);
resolver.setValue(image.id, image.urls.regular);
data.push(new ImageId().setSource(id).setId(image.id));
}
return {
page: page,
data: data,
total: response.total,
}
});
}
public function load(id:String, thumb:Bool = false):Promise<ImageValue> {
return resolver.resolve(id, thumb);
}
} }

View File

@@ -4,8 +4,7 @@ import haxe.io.Bytes;
import ru.m.storage.SharedObjectDataStorage; import ru.m.storage.SharedObjectDataStorage;
@:provide class FileStorage extends SharedObjectDataStorage<Bytes, String> { @:provide class FileStorage extends SharedObjectDataStorage<Bytes, String> {
public function new() {
public function new() { super("file", new DefaultHelper());
super("file", new DefaultHelper()); }
}
} }

View File

@@ -7,42 +7,36 @@ import ru.m.puzzlez.proto.game.ImageId;
import ru.m.storage.SharedObjectDataStorage; import ru.m.storage.SharedObjectDataStorage;
class GameStorageHelper implements DataHelper<GameState, ImageId> { class GameStorageHelper implements DataHelper<GameState, ImageId> {
public function new() { public function new() {}
} public function idKey(id:ImageId):String {
return '${id.source}:${id.id}';
}
public function idKey(id:ImageId):String { public function keyId(key:String):ImageId {
return '${id.source}:${id.id}'; var keyArray = key.split(":");
} return new ImageId().setSource(keyArray[0]).setId(keyArray[1]);
}
public function keyId(key:String):ImageId { public function dataId(data:GameState):ImageId {
var keyArray = key.split(":"); return data.preset.image;
return new ImageId().setSource(keyArray[0]).setId(keyArray[1]); }
}
public function dataId(data:GameState):ImageId { public function dataBytes(data:GameState):Bytes {
return data.preset.image; return PacketUtil.toBytes(data);
} }
public function dataBytes(data:GameState):Bytes { public function bytesData(bytes:Bytes):GameState {
return PacketUtil.toBytes(data); return PacketUtil.fromBytes(bytes, GameState);
} }
public function bytesData(bytes:Bytes):GameState { public function dataMeta(data:GameState):DataMeta {
return PacketUtil.fromBytes(bytes, GameState); return ["status" => data.status, "date" => Date.now(),];
} }
public function dataMeta(data:GameState):DataMeta {
return [
"status" => data.status,
"date" => Date.now(),
];
}
} }
@:provide class GameStorage extends SharedObjectDataStorage<GameState, ImageId> { @:provide class GameStorage extends SharedObjectDataStorage<GameState, ImageId> {
public function new() {
public function new() { super("game", new GameStorageHelper());
super("game", new GameStorageHelper()); }
}
} }

View File

@@ -17,86 +17,85 @@ import ru.m.puzzlez.view.popup.BackgroundPopup;
import ru.m.puzzlez.view.popup.PreviewPopup; import ru.m.puzzlez.view.popup.PreviewPopup;
@:template class GameFrame extends FrameView<GameState> { @:template class GameFrame extends FrameView<GameState> {
public static var ID = "game"; public static var ID = "game";
@:view private var render:IRender; @:view private var render:IRender;
private var game:IGame; private var game:IGame;
@:provide var switcher:FrameSwitcher; @:provide var switcher:FrameSwitcher;
@:provide var storage:GameStorage; @:provide var storage:GameStorage;
@:provide var settings:Settings; @:provide var settings:Settings;
private var saveTimer:Timer; private var saveTimer:Timer;
public function new() { public function new() {
super(ID); super(ID);
}
override public function onShow(state:GameState):Void {
onHide();
if (state.online) {
// game = new NetworkGame(state);
} else {
game = new Game(state);
} }
game.events.connect(render.onGameEvent);
game.events.connect(onGameEvent);
render.actions.connect(game.action);
game.start();
}
override public function onShow(state:GameState):Void { override public function onHide():Void {
onHide(); if (saveTimer != null) {
if (state.online) { save();
//game = new NetworkGame(state);
} else {
game = new Game(state);
}
game.events.connect(render.onGameEvent);
game.events.connect(onGameEvent);
render.actions.connect(game.action);
game.start();
} }
if (game != null) {
render.actions.disconnect(game.action);
game.stop();
game.dispose();
game = null;
}
}
override public function onHide():Void { private function toSave():Void {
if (saveTimer != null) { if (saveTimer == null) {
save(); saveTimer = Timer.delay(save, 5000);
}
if (game != null) {
render.actions.disconnect(game.action);
game.stop();
game.dispose();
game = null;
}
} }
}
private function toSave():Void { private function save():Void {
if (saveTimer == null) { if (saveTimer != null) {
saveTimer = Timer.delay(save, 5000); saveTimer.stop();
} saveTimer = null;
} }
if (game != null) {
storage.save(game.state);
}
}
private function save():Void { private function onGameEvent(event:GameEvent):Void {
if (saveTimer != null) { if (event.hasStart() || event.hasAction()) {
saveTimer.stop(); toSave();
saveTimer = null;
}
if (game != null) {
storage.save(game.state);
}
} }
}
private function onGameEvent(event:GameEvent):Void { private function showPreview():Void {
if (event.hasStart() || event.hasAction()) { PreviewPopup.instance.showPreview(game.state);
toSave(); }
}
}
private function showPreview():Void { private function choiseBackground():Void {
PreviewPopup.instance.showPreview(game.state); BackgroundPopup.instance.choise(settings.background).then(background -> {
} if (background != null) {
settings.background = background;
render.toRedraw();
}
});
}
private function choiseBackground():Void { private function back():Void {
BackgroundPopup.instance.choise(settings.background).then(background -> { (game.state.status == GameStatus.COMPLETE ? Promise.promise(true) : ConfirmView.confirm("Exit?")).then(result -> {
if (background != null) { if (result) {
settings.background = background; switcher.change(StartFrame.ID);
render.toRedraw(); }
} });
}); }
}
private function back():Void {
(game.state.status == GameStatus.COMPLETE ? Promise.promise(true) : ConfirmView.confirm("Exit?"))
.then(result -> {
if (result) {
switcher.change(StartFrame.ID);
}
});
}
} }

View File

@@ -10,37 +10,37 @@ import ru.m.data.DataSource.Filter;
import hw.view.frame.FrameView; import hw.view.frame.FrameView;
typedef GameListConfig = { typedef GameListConfig = {
var title:String; var title:String;
var source:DataSource<GameState>; var source:DataSource<GameState>;
@:optional var filter:Filter; @:optional var filter:Filter;
} }
@:template class GameListFrame extends FrameView<GameListConfig> { @:template class GameListFrame extends FrameView<GameListConfig> {
public static var ID(default, never) = "game_list"; public static var ID(default, never) = "game_list";
@:view var header:LabelView; @:view var header:LabelView;
@:view var games:DataList<GameState, GameStateView>; @:view var games:DataList<GameState, GameStateView>;
@:provide var switcher:FrameSwitcher; @:provide var switcher:FrameSwitcher;
public function new() { public function new() {
super(ID); super(ID);
}
override public function onShow(data:GameListConfig):Void {
games.reset();
if (data != null) {
header.text = data.title;
games.page.filter = data.filter;
games.source = data.source;
games.refresh();
} }
}
override public function onShow(data:GameListConfig):Void { public function start(state:GameState):Void {
games.reset(); switcher.change(GameFrame.ID, state);
if (data != null) { }
header.text = data.title;
games.page.filter = data.filter;
games.source = data.source;
games.refresh();
}
}
public function start(state:GameState):Void { private function back():Void {
switcher.change(GameFrame.ID, state); switcher.change(StartFrame.ID);
} }
private function back():Void {
switcher.change(StartFrame.ID);
}
} }

View File

@@ -10,38 +10,38 @@ import ru.m.puzzlez.image.ImageSourceBundle;
import ru.m.puzzlez.proto.game.ImageId; import ru.m.puzzlez.proto.game.ImageId;
typedef ImageListConfig = { typedef ImageListConfig = {
var title:String; var title:String;
var sourceId:String; var sourceId:String;
@:optional var filter:Filter; @:optional var filter:Filter;
} }
@:template class ImageListFrame extends FrameView<ImageListConfig> { @:template class ImageListFrame extends FrameView<ImageListConfig> {
public static var ID = "image_list"; public static var ID = "image_list";
@:view var header:LabelView; @:view var header:LabelView;
@:view var images:DataList<ImageId, ImageIdView>; @:view var images:DataList<ImageId, ImageIdView>;
@:provide var switcher:FrameSwitcher; @:provide var switcher:FrameSwitcher;
@:provide var sourceBundle:ImageSourceBundle; @:provide var sourceBundle:ImageSourceBundle;
public function new() { public function new() {
super(ID); super(ID);
}
override public function onShow(data:ImageListConfig):Void {
images.reset();
if (data != null) {
header.text = data.title;
images.page.filter = data.filter;
images.source = sourceBundle.get(data.sourceId);
images.refresh();
} }
}
override public function onShow(data:ImageListConfig):Void { private function start(imageId:ImageId):Void {
images.reset(); switcher.change(PresetFrame.ID, imageId);
if (data != null) { }
header.text = data.title;
images.page.filter = data.filter;
images.source = sourceBundle.get(data.sourceId);
images.refresh();
}
}
private function start(imageId:ImageId):Void { private function back():Void {
switcher.change(PresetFrame.ID, imageId); switcher.change(StartFrame.ID);
} }
private function back():Void {
switcher.change(StartFrame.ID);
}
} }

View File

@@ -10,57 +10,57 @@ import ru.m.puzzlez.proto.game.ImageId;
import ru.m.puzzlez.view.common.PresetView; import ru.m.puzzlez.view.common.PresetView;
@:template class PresetFrame extends FrameView<ImageId> { @:template class PresetFrame extends FrameView<ImageId> {
public static var ID = "preset"; public static var ID = "preset";
@:view("image") var imageView:PresetView; @:view("image") var imageView:PresetView;
@:view("sizes") var sizesView:DataView<IntPoint, ToggleButtonView>; @:view("sizes") var sizesView:DataView<IntPoint, ToggleButtonView>;
@:provide var switcher:FrameSwitcher; @:provide var switcher:FrameSwitcher;
//@:provide var network:Network;
private var imageId:ImageId; // @:provide var network:Network;
private var imageId:ImageId;
public function new() { public function new() {
super(ID); super(ID);
sizesView.data = [ sizesView.data = [
new IntPoint(3, 2), new IntPoint(3, 2),
new IntPoint(4, 3), new IntPoint(4, 3),
new IntPoint(5, 4), new IntPoint(5, 4),
new IntPoint(6, 5), new IntPoint(6, 5),
new IntPoint(7, 6), new IntPoint(7, 6),
new IntPoint(8, 7), new IntPoint(8, 7),
new IntPoint(10, 8), new IntPoint(10, 8),
]; ];
}
private function factory(index:Int, size:IntPoint):ToggleButtonView {
var result = new ToggleButtonView();
result.text = '${size.x}x${size.y}';
return result;
}
private function selectSize(size:IntPoint):Void {
imageView.state = GameUtil.buildState(GameUtil.buildPreset(imageId, size.x, size.y));
for (i in 0...sizesView.dataViews.length) {
sizesView.dataViews[i].on = size == sizesView.data[i];
} }
}
private function factory(index:Int, size:IntPoint):ToggleButtonView { override public function onShow(data:ImageId):Void {
var result = new ToggleButtonView(); super.onShow(data);
result.text = '${size.x}x${size.y}'; imageId = data;
return result; selectSize(sizesView.data[sizesView.data.length - 1]);
} }
private function selectSize(size:IntPoint):Void { private function start(online:Bool = false):Void {
imageView.state = GameUtil.buildState(GameUtil.buildPreset(imageId, size.x, size.y)); if (online) {
for (i in 0...sizesView.dataViews.length) { // network.createGame(imageView.state.preset).then(state -> switcher.change(GameFrame.ID, state));
sizesView.dataViews[i].on = size == sizesView.data[i]; } else {
} switcher.change(GameFrame.ID, imageView.state);
} }
}
override public function onShow(data:ImageId):Void { private function back():Void {
super.onShow(data); switcher.change(ImageListFrame.ID);
imageId = data; }
selectSize(sizesView.data[sizesView.data.length - 1]);
}
private function start(online:Bool = false):Void {
if (online) {
//network.createGame(imageView.state.preset).then(state -> switcher.change(GameFrame.ID, state));
} else {
switcher.change(GameFrame.ID, imageView.state);
}
}
private function back():Void {
switcher.change(ImageListFrame.ID);
}
} }

View File

@@ -9,28 +9,27 @@ import hw.view.frame.FrameSwitcher;
import hw.view.group.VGroupView; import hw.view.group.VGroupView;
@:template class PuzzlezAppView extends VGroupView { @:template class PuzzlezAppView extends VGroupView {
@:view("switcher") var switcherView:FrameSwitcher;
@:view("fullscreen") var fullscreenButton:ButtonView;
@:view("user") var userLabel:LabelView;
@:view("switcher") var switcherView:FrameSwitcher; @:provide static var switcher:FrameSwitcher;
@:view("fullscreen") var fullscreenButton:ButtonView; @:provide static var app:App;
@:view("user") var userLabel:LabelView;
@:provide static var switcher:FrameSwitcher; public function new() {
@:provide static var app:App; super();
fullscreenButton.visible = app.fullScreenSupport;
public function new() { app.fullScreenSignal.connect(fs -> fullscreenButton.style = 'icon.${fs ? "compress" : "expand"}');
super(); switcher = switcherView;
fullscreenButton.visible = app.fullScreenSupport; switcher.change(StartFrame.ID);
app.fullScreenSignal.connect(fs -> fullscreenButton.style = 'icon.${fs ? "compress" : "expand"}'); stage.addEventListener(KeyboardEvent.KEY_DOWN, (event:KeyboardEvent) -> {
switcher = switcherView; switch event.keyCode {
switcher.change(StartFrame.ID); case Keyboard.ESCAPE:
stage.addEventListener(KeyboardEvent.KEY_DOWN, (event:KeyboardEvent) -> { // switcher.change(StartFrame.ID);
switch event.keyCode { case Keyboard.F:
case Keyboard.ESCAPE: app.fullScreen = !app.fullScreen;
//switcher.change(StartFrame.ID); case _:
case Keyboard.F: }
app.fullScreen = !app.fullScreen; });
case _: }
}
});
}
} }

View File

@@ -4,79 +4,79 @@ import hw.view.data.DataView;
import hw.view.form.ButtonView; import hw.view.form.ButtonView;
import hw.view.frame.FrameSwitcher; import hw.view.frame.FrameSwitcher;
import hw.view.frame.FrameView; import hw.view.frame.FrameView;
import ru.m.pixabay.PixabayApi; import ru.m.api.PixabayApi;
import ru.m.puzzlez.FileUtil; import ru.m.puzzlez.FileUtil;
import ru.m.puzzlez.proto.game.GameStatus; import ru.m.puzzlez.proto.game.GameStatus;
import ru.m.puzzlez.storage.FileStorage; import ru.m.puzzlez.storage.FileStorage;
import ru.m.puzzlez.storage.GameStorage; import ru.m.puzzlez.storage.GameStorage;
import ru.m.puzzlez.view.ImageListFrame;
import ru.m.puzzlez.view.GameListFrame; import ru.m.puzzlez.view.GameListFrame;
import ru.m.puzzlez.view.ImageListFrame;
import ru.m.update.Updater; import ru.m.update.Updater;
@:template class StartFrame extends FrameView<Dynamic> { @:template class StartFrame extends FrameView<Dynamic> {
public static var ID = "start"; public static var ID = "start";
@:view var sources:DataView<ImageListConfig, ButtonView>; @:view var sources:DataView<ImageListConfig, ButtonView>;
@:view var startedButton:ButtonView; @:view var startedButton:ButtonView;
@:view var completedButton:ButtonView; @:view var completedButton:ButtonView;
@:view var updateButton:ButtonView; @:view var updateButton:ButtonView;
@:provide var switcher:FrameSwitcher; @:provide var switcher:FrameSwitcher;
@:provide static var appUpdater:Updater;
@:provide var fileStorage:FileStorage;
@:provide var gameStorage:GameStorage;
private var fileSource:ImageListConfig = {title: "Files", sourceId: "file"}; @:provide static var appUpdater:Updater;
private var startedGames:GameListConfig = {title: "Started", source: gameStorage, filter: ["status" => GameStatus.STARTED]};
private var completedGames:GameListConfig = {title: "Completed", source: gameStorage, filter: ["status" => GameStatus.COMPLETE]};
public function new() { @:provide var fileStorage:FileStorage;
super(ID); @:provide var gameStorage:GameStorage;
var data:Array<ImageListConfig> = [];
// data.push({title: "Assets", sourceId: "asset"}); private var fileSource:ImageListConfig = {title: "Files", sourceId: "file"};
data.push(fileSource); private var startedGames:GameListConfig = {title: "Started", source: gameStorage, filter: ["status" => GameStatus.STARTED]};
for (type in AbstractEnumTools.getValues(PixabayCategory)) { private var completedGames:GameListConfig = {title: "Completed", source: gameStorage, filter: ["status" => GameStatus.COMPLETE]};
data.push({title: type, sourceId: "unsplash", filter: ["category" => type]});
} public function new() {
sources.data = data; super(ID);
var data:Array<ImageListConfig> = [];
// data.push({title: "Assets", sourceId: "asset"});
data.push(fileSource);
for (type in AbstractEnumTools.getValues(PixabayCategory)) {
data.push({title: type, sourceId: "unsplash", filter: ["category" => type]});
} }
sources.data = data;
}
private function sourceViewFactory(index:Int, source:ImageListConfig):ButtonView { private function sourceViewFactory(index:Int, source:ImageListConfig):ButtonView {
var result = new ButtonView(); var result = new ButtonView();
result.text = source.title; result.text = source.title;
return result; return result;
} }
public function upload():Void { public function upload():Void {
FileUtil.browse() FileUtil.browse().pipe((data:FileContent) -> fileStorage.save(data.content)).then(_ -> showSource(fileSource));
.pipe((data:FileContent) -> fileStorage.save(data.content)) }
.then(_ -> showSource(fileSource));
}
public function showSource(config:ImageListConfig):Void { public function showSource(config:ImageListConfig):Void {
switcher.change(ImageListFrame.ID, config); switcher.change(ImageListFrame.ID, config);
} }
public function showGames(config:GameListConfig):Void { public function showGames(config:GameListConfig):Void {
switcher.change(GameListFrame.ID, config); switcher.change(GameListFrame.ID, config);
} }
override public function onShow(data:Dynamic):Void { override public function onShow(data:Dynamic):Void {
appUpdater.check().then((info:Null<PackageInfo>) -> { appUpdater.check().then((info:Null<PackageInfo>) -> {
if (info != null) { if (info != null) {
updateButton.visible = true; updateButton.visible = true;
updateButton.text = 'Update ${info.version}'; updateButton.text = 'Update ${info.version}';
} }
}).catchError(error -> L.w('Update', 'failed: ${error}')); }).catchError(error -> L.w('Update', 'failed: ${error}'));
refresh(); refresh();
} }
private function refresh():Void { private function refresh():Void {
gameStorage.getIndexPage({index:0, count:0, filter:startedGames.filter}).then(response -> { gameStorage.getIndexPage({index: 0, count: 0, filter: startedGames.filter}).then(response -> {
startedButton.text = response.total > 0 ? 'Started (${response.total})' : 'Started'; startedButton.text = response.total > 0 ? 'Started (${response.total})' : 'Started';
}); });
gameStorage.getIndexPage({index:0, count:0, filter:completedGames.filter}).then(response -> { gameStorage.getIndexPage({index: 0, count: 0, filter: completedGames.filter}).then(response -> {
completedButton.text = response.total > 0 ? 'Completed (${response.total})' : 'Completed'; completedButton.text = response.total > 0 ? 'Completed (${response.total})' : 'Completed';
}); });
} }
} }

View File

@@ -7,28 +7,28 @@ import ru.m.puzzlez.image.GameUtil;
import ru.m.puzzlez.proto.game.GameState; import ru.m.puzzlez.proto.game.GameState;
@:template class GameStateView extends GroupView { @:template class GameStateView extends GroupView {
public var state(default, set):GameState; public var state(default, set):GameState;
private function set_state(value:GameState):GameState { private function set_state(value:GameState):GameState {
state = value; state = value;
image.imageId = state.preset.image; image.imageId = state.preset.image;
var progress = GameUtil.calcProgress(state); var progress = GameUtil.calcProgress(state);
label.text = '${progress.complete}/${progress.total}'; label.text = '${progress.complete}/${progress.total}';
return state; return state;
}
@:view var image:ImageIdView;
@:view var label:LabelView;
public function new(?state:GameState, ?style:StyleId) {
super();
this.style = style;
if (state != null) {
this.state = state;
} }
}
@:view var image:ImageIdView; public static function factory(index:Int, value:GameState):GameStateView {
@:view var label:LabelView; return new GameStateView(value, "view");
}
public function new(?state:GameState, ?style: StyleId) {
super();
this.style = style;
if (state != null) {
this.state = state;
}
}
public static function factory(index:Int, value:GameState):GameStateView {
return new GameStateView(value, "view");
}
} }

View File

@@ -8,41 +8,38 @@ import ru.m.puzzlez.proto.game.ImageId;
import ru.m.view.LoadingWrapper; import ru.m.view.LoadingWrapper;
@:template class ImageIdView extends GroupView { @:template class ImageIdView extends GroupView {
public var thumb:Bool = false;
public var thumb:Bool = false; public var imageId(default, set):ImageId;
public var imageId(default, set):ImageId; private function set_imageId(value:ImageId):ImageId {
private function set_imageId(value:ImageId):ImageId { imageId = value;
imageId = value; var imageData = ImageData.fromImageId(imageId);
var imageData = ImageData if (thumb) {
.fromImageId(imageId); imageData = imageData.withThumb();
if (thumb) {
imageData = imageData.withThumb();
}
loading.promise = imageData
.resolve()
.then(data -> this.imageView.image = data);
return imageId;
} }
loading.promise = imageData.resolve().then(data -> this.imageView.image = data);
return imageId;
}
@:view("image") var imageView:ImageView; @:view("image") var imageView:ImageView;
private var loading:LoadingWrapper; private var loading:LoadingWrapper;
public function new(?imageId:ImageId, ?style: StyleId, ?thumb: Bool = false) { public function new(?imageId:ImageId, ?style:StyleId, ?thumb:Bool = false) {
super(); super();
this.style = style; this.style = style;
this.thumb = thumb; this.thumb = thumb;
loading = new LoadingWrapper(this); loading = new LoadingWrapper(this);
if (imageId != null) { if (imageId != null) {
this.imageId = imageId; this.imageId = imageId;
}
} }
}
public static function factory(index:Int, value:ImageId):ImageIdView { public static function factory(index:Int, value:ImageId):ImageIdView {
return new ImageIdView(value, "view"); return new ImageIdView(value, "view");
} }
public static function factoryThumb(index:Int, value:ImageId):ImageIdView { public static function factoryThumb(index:Int, value:ImageId):ImageIdView {
return new ImageIdView(value, "view", true); return new ImageIdView(value, "view", true);
} }
} }

View File

@@ -12,77 +12,77 @@ import ru.m.puzzlez.render.RenderUtil;
import ru.m.puzzlez.wrap.RectangleExt; import ru.m.puzzlez.wrap.RectangleExt;
class PresetView extends GroupView { class PresetView extends GroupView {
@:provide static var builder:IPartBuilder; @:provide static var builder:IPartBuilder;
public var scale(get, set):Float; public var scale(get, set):Float;
private function get_scale():Float { private function get_scale():Float {
return table.scaleX; return table.scaleX;
}
private function set_scale(value:Float):Float {
var result = table.scaleX = table.scaleY = value;
table.x = (width - state.preset.imageRect.width * value) / 2;
table.y = (height - state.preset.imageRect.height * value) / 2;
return result;
}
public var state(default, set):GameState;
private function set_state(value:GameState):GameState {
state = value;
this.image = null;
table.graphics.clear();
loading.promise = ImageData.fromImageId(state.preset.image).resolve().then(image -> {
this.image = RenderUtil.cropImage(image, state.preset.imageRect);
toRedraw();
toUpdate();
});
return state;
}
private var loading:LoadingWrapper;
public function new() {
super();
table = new Shape();
content.addChild(table);
loading = new LoadingWrapper(this);
}
private var table:Shape;
private var image:BitmapData;
override public function redraw():Void {
if (state == null || image == null) {
return;
} }
var preset = state.preset;
var partWidth = preset.imageRect.width / preset.grid.x;
var partHeight = preset.imageRect.height / preset.grid.y;
var graphics:Graphics = table.graphics;
graphics.clear();
graphics.beginBitmapFill(image, null, false, true);
graphics.drawRect(0, 0, preset.imageRect.width, preset.imageRect.height);
graphics.endFill();
private function set_scale(value:Float):Float { for (part in state.parts) {
var result = table.scaleX = table.scaleY = value; var rect = cast(part.rect, RectangleExt).clone();
table.x = (width - state.preset.imageRect.width * value) / 2; rect.x = part.point.x * part.rect.width;
table.y = (height - state.preset.imageRect.height * value) / 2; rect.y = part.point.y * part.rect.height;
return result; var path = builder.build(rect, part.bounds);
for (value in RenderUtil.borderSettings) {
graphics.lineStyle(1, value.color, value.opacity);
path.move(value.offset.x, value.offset.y).draw(graphics);
graphics.lineStyle();
}
} }
}
public var state(default, set):GameState; override public function update():Void {
super.update();
private function set_state(value:GameState):GameState { if (state != null) {
state = value; scale = Math.min(width / state.preset.imageRect.width, height / state.preset.imageRect.height);
this.image = null;
table.graphics.clear();
loading.promise = ImageData.fromImageId(state.preset.image).resolve().then(image -> {
this.image = RenderUtil.cropImage(image, state.preset.imageRect);
toRedraw();
toUpdate();
});
return state;
}
private var loading:LoadingWrapper;
public function new() {
super();
table = new Shape();
content.addChild(table);
loading = new LoadingWrapper(this);
}
private var table:Shape;
private var image:BitmapData;
override public function redraw():Void {
if (state == null || image == null) {
return;
}
var preset = state.preset;
var partWidth = preset.imageRect.width / preset.grid.x;
var partHeight = preset.imageRect.height / preset.grid.y;
var graphics:Graphics = table.graphics;
graphics.clear();
graphics.beginBitmapFill(image, null, false, true);
graphics.drawRect(0, 0, preset.imageRect.width, preset.imageRect.height);
graphics.endFill();
for (part in state.parts) {
var rect = cast(part.rect, RectangleExt).clone();
rect.x = part.point.x * part.rect.width;
rect.y = part.point.y * part.rect.height;
var path = builder.build(rect, part.bounds);
for (value in RenderUtil.borderSettings) {
graphics.lineStyle(1, value.color, value.opacity);
path.move(value.offset.x, value.offset.y).draw(graphics);
graphics.lineStyle();
}
}
}
override public function update():Void {
super.update();
if (state != null) {
scale = Math.min(width / state.preset.imageRect.width, height / state.preset.imageRect.height);
}
} }
}
} }

View File

@@ -15,82 +15,66 @@ import ru.m.puzzlez.proto.game.ImageId;
import ru.m.puzzlez.render.Background; import ru.m.puzzlez.render.Background;
@:singleton @:template class BackgroundPopup extends PopupView<Background> { @:singleton @:template class BackgroundPopup extends PopupView<Background> {
private static var colorsList:Array<Color> = [
'#FFFFFF', '#001f3f', '#0074D9', '#7FDBFF', '#39CCCC', '#3D9970', '#2ECC40', '#01FF70', '#FFDC00', '#FF851B', '#FF4136', '#85144b', '#F012BE', '#B10DC9',
'#111111', '#AAAAAA', '#DDDDDD',
];
private static var colorsList:Array<Color> = [ @:view("selected") var selectedView:SpriteView;
'#FFFFFF', @:view("colors") var colorsView:DataView<Color, ButtonView>;
'#001f3f', @:view("textures") var texturesView:DataView<ImageId, ButtonView>;
'#0074D9',
'#7FDBFF',
'#39CCCC',
'#3D9970',
'#2ECC40',
'#01FF70',
'#FFDC00',
'#FF851B',
'#FF4136',
'#85144b',
'#F012BE',
'#B10DC9',
'#111111',
'#AAAAAA',
'#DDDDDD',
];
@:view("selected") var selectedView:SpriteView; public var selected(default, set):Background;
@:view("colors") var colorsView:DataView<Color, ButtonView>;
@:view("textures") var texturesView:DataView<ImageId, ButtonView>;
public var selected(default, set):Background; // @:provide static var imageStorage:ImageStorage;
// @:provide static var imageStorage:ImageStorage; public function new() {
super();
public function new() { colorsView.data = colorsList;
super(); var textures = [];
colorsView.data = colorsList; for (name in Assets.list(AssetType.IMAGE)) {
var textures = []; if (StringTools.startsWith(name, 'resources/texture')) {
for (name in Assets.list(AssetType.IMAGE)) { textures.push(new ImageId().setSource('asset').setId(name));
if (StringTools.startsWith(name, 'resources/texture')) { }
textures.push(new ImageId().setSource('asset').setId(name));
}
}
texturesView.data = textures;
} }
texturesView.data = textures;
}
private function set_selected(value:Background):Background { private function set_selected(value:Background):Background {
selected = value; selected = value;
switch selected { switch selected {
case NONE: case NONE:
selectedView.skin = null; selectedView.skin = null;
case COLOR(color): case COLOR(color):
selectedView.skin = Skin.color(color); selectedView.skin = Skin.color(color);
case IMAGE(id): case IMAGE(id):
ImageData.fromImageId(id).resolve().then(result -> { ImageData.fromImageId(id).resolve().then(result -> {
selectedView.skin = Skin.bitmap(result, REPEAT); selectedView.skin = Skin.bitmap(result, REPEAT);
selectedView.toRedraw(); selectedView.toRedraw();
}); });
}
selectedView.toRedraw();
return selected;
} }
selectedView.toRedraw();
return selected;
}
private function colorButtonFactory(index:Int, color:Color):ButtonView { private function colorButtonFactory(index:Int, color:Color):ButtonView {
var result = new ButtonView(); var result = new ButtonView();
result.style = "button.background"; result.style = "button.background";
result.text = " "; result.text = " ";
result.skin = Skin.buttonColor(color); result.skin = Skin.buttonColor(color);
return result; return result;
} }
private function textureButtonFactory(index:Int, imageId:ImageId):ButtonView { private function textureButtonFactory(index:Int, imageId:ImageId):ButtonView {
var result = new ButtonView(); var result = new ButtonView();
result.style = "button.background"; result.style = "button.background";
result.text = " "; result.text = " ";
result.skin = Skin.buttonBitmap(Assets.getBitmapData(imageId.id), REPEAT); result.skin = Skin.buttonBitmap(Assets.getBitmapData(imageId.id), REPEAT);
return result; return result;
} }
public function choise(current:Background):Promise<Background> { public function choise(current:Background):Promise<Background> {
selected = current; selected = current;
return show(); return show();
} }
} }

View File

@@ -7,16 +7,15 @@ import promhx.Promise;
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("preview") var previewView:PresetView;
@:view("preview") var previewView:PresetView; public function new() {
super();
content.addEventListener(MouseEvent.CLICK, _ -> close(null));
}
public function new() { public function showPreview(state:GameState):Promise<Dynamic> {
super(); previewView.state = state;
content.addEventListener(MouseEvent.CLICK, _ -> close(null)); return show();
} }
public function showPreview(state:GameState):Promise<Dynamic> {
previewView.state = state;
return show();
}
} }

View File

@@ -10,59 +10,58 @@ using StringTools;
using hw.color.ColorUtil; using hw.color.ColorUtil;
@:style class ButtonSVGSkin implements ISkin<ButtonView> { @:style class ButtonSVGSkin implements ISkin<ButtonView> {
@:style(null) public var svg:String;
@:style(0) public var color:Null<Color>;
@:style(false) public var solid:Null<Bool>;
@:style(null) public var svg:String; private var svgs:Map<ButtonState, SVG>;
@:style(0) public var color:Null<Color>; private var needUpdate:Bool;
@:style(false) public var solid:Null<Bool>;
private var svgs:Map<ButtonState, SVG>; public function new(?svg:String, ?color:Color, ?solid:Bool) {
private var needUpdate:Bool; this.svg = svg;
this.color = color;
this.solid = solid;
this.needUpdate = true;
}
public function new(?svg:String, ?color:Color, ?solid:Bool) { private inline function buildSVG(color:Color):SVG {
this.svg = svg; return new SVG(svg.replace("currentColor", '#${color}'));
this.color = color; }
this.solid = solid;
this.needUpdate = true; private function update():Void {
if (needUpdate && svg != null) {
var color = color;
if (solid) {
color = color.multiply(1.5);
}
svgs = new Map();
svgs.set(UP, buildSVG(color));
svgs.set(DOWN, buildSVG(color.diff(-24)));
svgs.set(OVER, buildSVG(color.diff(24)));
svgs.set(DISABLED, buildSVG(color.grey()));
needUpdate = false;
} }
}
private inline function buildSVG(color:Color):SVG { public function draw(view:ButtonView):Void {
return new SVG(svg.replace("currentColor", '#${color}')); update();
var svg = svgs.get(view.state);
var graphics = view.content.graphics;
var color = this.color;
if (Std.is(view, ToggleButtonView)) {
if (!cast(view, ToggleButtonView).on) {
color = color.multiply(0.5);
}
} }
graphics.beginFill(0, 0);
private function update():Void { graphics.drawRect(0, 0, view.width, view.height);
if (needUpdate && svg != null) { graphics.beginFill(color);
var color = color; if (!solid) {
if (solid) { graphics.lineStyle(2, color.multiply(1.5));
color = color.multiply(1.5);
}
svgs = new Map();
svgs.set(UP, buildSVG(color));
svgs.set(DOWN, buildSVG(color.diff(-24)));
svgs.set(OVER, buildSVG(color.diff(24)));
svgs.set(DISABLED, buildSVG(color.grey()));
needUpdate = false;
}
}
public function draw(view:ButtonView):Void {
update();
var svg = svgs.get(view.state);
var graphics = view.content.graphics;
var color = this.color;
if (Std.is(view, ToggleButtonView)) {
if (!cast(view, ToggleButtonView).on) {
color = color.multiply(0.5);
}
}
graphics.beginFill(0, 0);
graphics.drawRect(0, 0, view.width, view.height);
graphics.beginFill(color);
if (!solid) {
graphics.lineStyle(2, color.multiply(1.5));
}
// ToDo: padding
svg.render(graphics, 0, 0, Std.int(view.width * 0.8), Std.int(view.height * 0.8));
graphics.lineStyle();
graphics.endFill();
} }
// ToDo: padding
svg.render(graphics, 0, 0, Std.int(view.width * 0.8), Std.int(view.height * 0.8));
graphics.lineStyle();
graphics.endFill();
}
} }

View File

@@ -11,167 +11,165 @@ import ru.m.data.DataSource;
typedef DataMeta = Map<String, Dynamic>; typedef DataMeta = Map<String, Dynamic>;
typedef IdMeta<I> = { typedef IdMeta<I> = {
var id:I; var id:I;
var meta:DataMeta; var meta:DataMeta;
} }
interface DataHelper<D, I> { interface DataHelper<D, I> {
public function idKey(id:I):String; public function idKey(id:I):String;
public function keyId(key:String):I; public function keyId(key:String):I;
public function dataId(data:D):I; public function dataId(data:D):I;
public function dataBytes(data:D):Bytes; public function dataBytes(data:D):Bytes;
public function bytesData(bytes:Bytes):D; public function bytesData(bytes:Bytes):D;
public function dataMeta(data:D):DataMeta; public function dataMeta(data:D):DataMeta;
} }
class DefaultHelper implements DataHelper<Bytes, String> { class DefaultHelper implements DataHelper<Bytes, String> {
public function new() { public function new() {}
} public function idKey(id:String):String {
return id;
}
public function idKey(id:String):String { public function keyId(key:String):String {
return id; return key;
} }
public function keyId(key:String):String { public function dataId(data:Bytes):String {
return key; return Md5.make(data).toHex();
} }
public function dataId(data:Bytes):String { public function dataBytes(data:Bytes):Bytes {
return Md5.make(data).toHex(); return data;
} }
public function dataBytes(data:Bytes):Bytes { public function bytesData(bytes:Bytes):Bytes {
return data; return bytes;
} }
public function bytesData(bytes:Bytes):Bytes { public function dataMeta(data:Bytes):DataMeta {
return bytes; return ["date" => Date.now()];
} }
public function dataMeta(data:Bytes):DataMeta {
return ["date" => Date.now()];
}
} }
class SharedObjectDataStorage<D, I> implements DataStorage<D, I> { class SharedObjectDataStorage<D, I> implements DataStorage<D, I> {
private static var DATA_KEY:String = "data"; private static var DATA_KEY:String = "data";
public var name(default, null):String; public var name(default, null):String;
public var version(default, null):Int; public var version(default, null):Int;
private var helper:DataHelper<D, I>; private var helper:DataHelper<D, I>;
private var _indexObject:SharedObject; private var _indexObject:SharedObject;
private var indexObject(get, null):SharedObject; private var indexObject(get, null):SharedObject;
private function get_indexObject():SharedObject { private function get_indexObject():SharedObject {
if (_indexObject == null) { if (_indexObject == null) {
_indexObject = SharedObject.getLocal('${name}/${version}/index'); _indexObject = SharedObject.getLocal('${name}/${version}/index');
}
return _indexObject;
}
public function new(name:String, helper:DataHelper<D, I>, version:Int = 1) {
this.name = name;
this.version = version;
this.helper = helper;
}
private function resolveDataObject(id:I):SharedObject {
var idKey = helper.idKey(id);
return SharedObject.getLocal('${name}/${version}/${Md5.encode(idKey)}');
}
private function applyFilter(filter:Null<Filter>, meta:DataMeta):Bool {
if (filter != null) {
for (k in filter.keys()) {
if (meta.exists(k) && meta.get(k) != filter.get(k)) {
return false;
} }
return _indexObject; }
} }
return true;
}
public function new(name:String, helper:DataHelper<D, I>, version:Int = 1) { private function applyOrder(order:Order, values:Array<IdMeta<I>>):Void {
this.name = name; if (order != null) {
this.version = version; values.sort((a:IdMeta<I>, b:IdMeta<I>) -> {
this.helper = helper; for (item in order) {
} var av = a.meta.get(item.field);
var bv = b.meta.get(item.field);
private function resolveDataObject(id:I):SharedObject { if (av > bv) {
var idKey = helper.idKey(id); return item.reverse ? -1 : 1;
return SharedObject.getLocal('${name}/${version}/${Md5.encode(idKey)}'); } else if (av < bv) {
} return item.reverse ? 1 : -1;
}
private function applyFilter(filter:Null<Filter>, meta:DataMeta):Bool {
if (filter != null) {
for (k in filter.keys()) {
if (meta.exists(k) && meta.get(k) != filter.get(k)) {
return false;
}
}
} }
return true; return 0;
});
} }
}
private function applyOrder(order:Order, values:Array<IdMeta<I>>):Void { public function getIndexPage(page:Page):Promise<DataPage<I>> {
if (order != null) { var data = indexObject.data;
values.sort((a:IdMeta<I>, b:IdMeta<I>) -> { var values:Array<IdMeta<I>> = [];
for (item in order) { for (k in Reflect.fields(data)) {
var av = a.meta.get(item.field); var meta = Unserializer.run(Reflect.field(data, k));
var bv = b.meta.get(item.field); if (applyFilter(page.filter, meta)) {
if (av > bv) { values.push({id: helper.keyId(k), meta: meta});
return item.reverse ? -1 : 1; }
} else if (av < bv) { }
return item.reverse ? 1 : -1; applyOrder(page.order, values);
} var result:Array<I> = values.slice(page.index * page.count, page.index * page.count + page.count).map(value -> value.id);
} return Promise.promise({
return 0; page: page,
}); total: values.length,
data: result,
});
}
public function getPage(page:Page):Promise<DataPage<D>> {
return getIndexPage(page).pipe((indexPage:DataPage<I>) -> {
return Promise.whenAll([for (id in indexPage.data) get(id)]).then((data:Array<D>) -> {
return {
page: indexPage.page,
total: indexPage.total,
data: data,
} }
} });
});
}
public function getIndexPage(page:Page):Promise<DataPage<I>> { public function get(id:I):Promise<D> {
var data = indexObject.data; var dataObject = resolveDataObject(id);
var values:Array<IdMeta<I>> = []; if (Reflect.hasField(dataObject.data, DATA_KEY)) {
for (k in Reflect.fields(data)) { return Promise.promise(helper.bytesData(Bytes.ofHex(Reflect.field(dataObject.data, DATA_KEY))));
var meta = Unserializer.run(Reflect.field(data, k));
if (applyFilter(page.filter, meta)) {
values.push({id: helper.keyId(k), meta: meta});
}
}
applyOrder(page.order, values);
var result:Array<I> = values.slice(page.index * page.count, page.index * page.count + page.count).map(value -> value.id);
return Promise.promise({
page: page,
total: values.length,
data: result,
});
} }
return Promise.promise(null);
}
public function getPage(page:Page):Promise<DataPage<D>> { public function save(item:D):Promise<D> {
return getIndexPage(page).pipe((indexPage:DataPage<I>) -> { var id = helper.dataId(item);
return Promise.whenAll([for (id in indexPage.data) get(id)]).then((data:Array<D>) -> { var dataObject = resolveDataObject(id);
return { dataObject.setProperty(DATA_KEY, helper.dataBytes(item).toHex());
page: indexPage.page, dataObject.flush();
total: indexPage.total, indexObject.setProperty(helper.idKey(id), Serializer.run(helper.dataMeta(item)));
data: data, indexObject.flush();
} return Promise.promise(item);
}); }
});
}
public function get(id:I):Promise<D> { public function delete(id:I):Promise<Bool> {
var dataObject = resolveDataObject(id); var dataObject = resolveDataObject(id);
if (Reflect.hasField(dataObject.data, DATA_KEY)) { if (Reflect.hasField(dataObject.data, DATA_KEY)) {
return Promise.promise(helper.bytesData(Bytes.ofHex(Reflect.field(dataObject.data, DATA_KEY)))); dataObject.clear();
} Reflect.deleteField(indexObject.data, helper.idKey(id));
return Promise.promise(null); indexObject.flush();
} return Promise.promise(true);
public function save(item:D):Promise<D> {
var id = helper.dataId(item);
var dataObject = resolveDataObject(id);
dataObject.setProperty(DATA_KEY, helper.dataBytes(item).toHex());
dataObject.flush();
indexObject.setProperty(helper.idKey(id), Serializer.run(helper.dataMeta(item)));
indexObject.flush();
return Promise.promise(item);
}
public function delete(id:I):Promise<Bool> {
var dataObject = resolveDataObject(id);
if (Reflect.hasField(dataObject.data, DATA_KEY)) {
dataObject.clear();
Reflect.deleteField(indexObject.data, helper.idKey(id));
indexObject.flush();
return Promise.promise(true);
}
return Promise.promise(false);
} }
return Promise.promise(false);
}
} }

View File

@@ -8,76 +8,72 @@ import ru.m.Device;
import ru.m.update.Version; import ru.m.update.Version;
@:enum abstract PackageType(String) from String to String { @:enum abstract PackageType(String) from String to String {
var APK = "apk"; var APK = "apk";
var DEB = "deb"; var DEB = "deb";
var EXE = "exe"; var EXE = "exe";
var ARCHIVE = "archive"; var ARCHIVE = "archive";
} }
typedef PackageInfo = { typedef PackageInfo = {
var platform:Platform; var platform:Platform;
var type:PackageType; var type:PackageType;
var path:String; var path:String;
var filename:String; var filename:String;
var url:String; var url:String;
var version:String; var version:String;
} }
typedef PackagesBundle = { typedef PackagesBundle = {
var name:String; var name:String;
var version:String; var version:String;
var packages:Array<PackageInfo>; var packages:Array<PackageInfo>;
} }
class Updater { class Updater {
private static inline var TAG = "Update";
private static inline var TAG = "Update"; public var type(get, null):PackageType;
public var bundle(get, null):Promise<PackagesBundle>;
public var type(get, null):PackageType; private function get_bundle():Promise<PackagesBundle> {
public var bundle(get, null):Promise<PackagesBundle>; if (bundle == null) {
bundle = new JsonLoader().GET(url);
}
return bundle;
}
private function get_bundle():Promise<PackagesBundle> { private var url:String;
if (bundle == null) { private var version:Version;
bundle = new JsonLoader().GET(url);
public function new(version:Version, url:String) {
this.url = url;
this.version = version;
}
private function get_type():PackageType {
return switch Device.platform {
case ANDROID: APK;
case LINUX: DEB;
case WINDOWS: EXE;
case _: null;
}
}
public function check():Promise<Null<PackageInfo>> {
return bundle.then((bundle:PackagesBundle) -> Lambda.find(bundle.packages,
item -> (item.platform == Device.platform && item.type == type && version < Version.fromString(item.version))));
}
public function download():Promise<Bool> {
return bundle.then((bundle:PackagesBundle) -> {
for (item in bundle.packages) {
if (item.platform == Device.platform && item.type == type) {
L.i(TAG, 'download: ${item.url}');
Lib.getURL(new URLRequest(item.url));
return true;
} }
return bundle; }
} return false;
});
private var url:String; }
private var version:Version;
public function new(version:Version, url:String) {
this.url = url;
this.version = version;
}
private function get_type():PackageType {
return switch Device.platform {
case ANDROID: APK;
case LINUX: DEB;
case WINDOWS: EXE;
case _: null;
}
}
public function check():Promise<Null<PackageInfo>> {
return bundle.then((bundle:PackagesBundle) -> Lambda.find(bundle.packages, item -> (
item.platform == Device.platform &&
item.type == type &&
version < Version.fromString(item.version)
)));
}
public function download():Promise<Bool> {
return bundle.then((bundle:PackagesBundle) -> {
for (item in bundle.packages) {
if (item.platform == Device.platform && item.type == type) {
L.i(TAG, 'download: ${item.url}');
Lib.getURL(new URLRequest(item.url));
return true;
}
}
return false;
});
}
} }

View File

@@ -1,52 +1,45 @@
package ru.m.update; package ru.m.update;
abstract Version(Array<Int>) { abstract Version(Array<Int>) {
public function new(mayor:Int, minor:Int, patch:Int, snapshot:Bool = false) {
this = [mayor, minor, patch, snapshot ? -1 : 0,];
}
public function new(mayor:Int, minor:Int, patch:Int, snapshot:Bool = false) { @:arrayAccess public inline function get(key:Int)
this = [ return this[key];
mayor,
minor, public function compare(other:Version):Int {
patch, if (other == null) {
snapshot ? -1 : 0, return 1;
];
} }
for (i in 0...4) {
@:arrayAccess public inline function get(key:Int) return this[key]; var d = this[i] - other[i];
if (d != 0) {
public function compare(other:Version):Int { return d;
if (other == null) { }
return 1;
}
for (i in 0...4) {
var d = this[i] - other[i];
if (d != 0) {
return d;
}
}
return 0;
} }
return 0;
}
@:op(A > B) static function gt(a:Version, b:Version):Bool return a.compare(b) > 0; @:op(A > B) static function gt(a:Version, b:Version):Bool
return a.compare(b) > 0;
@:op(A == B) static function eq(a:Version, b:Version):Bool return a.compare(b) == 0; @:op(A == B) static function eq(a:Version, b:Version):Bool
return a.compare(b) == 0;
@:op(A < B) static function lt(a:Version, b:Version):Bool return a.compare(b) < 0; @:op(A < B) static function lt(a:Version, b:Version):Bool
return a.compare(b) < 0;
@:from public static function fromString(value:String):Version { @:from public static function fromString(value:String):Version {
var r = ~/(\d+)\.(\d+)\.(\d+)(-SNAPSHOT)?/; var r = ~/(\d+)\.(\d+)\.(\d+)(-SNAPSHOT)?/;
return if (r.match(value)) { return if (r.match(value)) {
new Version( new Version(Std.parseInt(r.matched(1)), Std.parseInt(r.matched(2)), Std.parseInt(r.matched(3)), r.matched(4) != null);
Std.parseInt(r.matched(1)), } else {
Std.parseInt(r.matched(2)), new Version(0, 0, 0);
Std.parseInt(r.matched(3)),
r.matched(4) != null
);
} else {
new Version(0, 0, 0);
}
} }
}
@:to public function toString():String { @:to public function toString():String {
return '${this[0]}.${this[1]}.${this[2]}${this[3] < 0 ? "-SNAPSHOT" : ""}'; return '${this[0]}.${this[1]}.${this[2]}${this[3] < 0 ? "-SNAPSHOT" : ""}';
} }
} }

View File

@@ -7,39 +7,39 @@ import ru.m.data.DataSource;
import ru.m.view.LoadingWrapper; import ru.m.view.LoadingWrapper;
@:template class DataList<T, V:View<Dynamic>> extends VGroupView { @:template class DataList<T, V:View<Dynamic>> extends VGroupView {
public var source:DataSource<T>;
public var page:Page;
public var source:DataSource<T>; @:view public var list(default, null):DataView<T, V>;
public var page:Page;
@:view public var list(default, null):DataView<T, V>; @:view private var paginator:PaginatorView;
@:view private var paginator:PaginatorView; private var loading:LoadingWrapper;
private var loading:LoadingWrapper;
public var data(default, set):DataPage<T>; public var data(default, set):DataPage<T>;
private function set_data(value:DataPage<T>):DataPage<T> { private function set_data(value:DataPage<T>):DataPage<T> {
data = value; data = value;
list.data = data.data; list.data = data.data;
paginator.page = data; paginator.page = data;
return data; return data;
} }
public function new() { public function new() {
super(); super();
loading = new LoadingWrapper(list); loading = new LoadingWrapper(list);
page = {index: 0, count: 6, order: [{field: "date", reverse: true}]}; page = {index: 0, count: 6, order: [{field: "date", reverse: true}]};
} }
public function refresh():Void { public function refresh():Void {
loading.promise = source.getPage(page).then(data -> this.data = data); loading.promise = source.getPage(page).then(data -> this.data = data);
} }
public function reset():Void { public function reset():Void {
page.index = 0; page.index = 0;
data = { data = {
page: page, page: page,
total: 0, total: 0,
data: [], data: [],
}; };
} }
} }

View File

@@ -10,98 +10,96 @@ import hw.view.text.TextView;
import promhx.Promise; import promhx.Promise;
enum State { enum State {
NONE; NONE;
LOADING; LOADING;
ERROR(error:Dynamic); ERROR(error:Dynamic);
} }
class LoadingWrapper { class LoadingWrapper {
public var promise(null, set):Promise<Dynamic>; public var promise(null, set):Promise<Dynamic>;
private function set_promise(value:Promise<Dynamic>):Promise<Dynamic> { private function set_promise(value:Promise<Dynamic>):Promise<Dynamic> {
state = LOADING; state = LOADING;
value value.then(_ -> state = NONE).catchError(error -> state = ERROR(error));
.then(_ -> state = NONE) return value;
.catchError(error -> state = ERROR(error)); }
return value;
public var state(default, set):State;
private function set_state(value:State):State {
if (state != value) {
state = value;
switch state {
case NONE:
overlay = null;
case LOADING:
overlay = loadingView;
case ERROR(error):
L.e("wrapper", "", error);
cast(errorView, TextView).text = Std.string(error);
overlay = errorView;
}
} }
return state;
}
public var state(default, set):State; private var view:IGroupView;
private function set_state(value:State):State { private var overlay(default, set):IView<Dynamic>;
if (state != value) {
state = value; private function set_overlay(value:IView<Dynamic>):IView<Dynamic> {
switch state { if (overlay != null && view.containsView(overlay)) {
case NONE: view.removeView(overlay);
overlay = null;
case LOADING:
overlay = loadingView;
case ERROR(error):
L.e("wrapper", "", error);
cast(errorView, TextView).text = Std.string(error);
overlay = errorView;
}
}
return state;
} }
overlay = value;
private var view:IGroupView; if (overlay != null) {
view.addView(overlay);
private var overlay(default, set):IView<Dynamic>;
private function set_overlay(value:IView<Dynamic>):IView<Dynamic> {
if (overlay != null && view.containsView(overlay)) {
view.removeView(overlay);
}
overlay = value;
if (overlay != null) {
view.addView(overlay);
}
return overlay;
} }
return overlay;
}
private var loadingView(get, null):IView<Dynamic>; private var loadingView(get, null):IView<Dynamic>;
private function get_loadingView():IView<Dynamic> { private function get_loadingView():IView<Dynamic> {
if (loadingView == null) { if (loadingView == null) {
loadingView = buildLoadingView(); loadingView = buildLoadingView();
}
return loadingView;
} }
return loadingView;
}
private var errorView(get, null):IView<Dynamic>; private var errorView(get, null):IView<Dynamic>;
private function get_errorView():IView<Dynamic> { private function get_errorView():IView<Dynamic> {
if (errorView == null) { if (errorView == null) {
errorView = buildErrorView(); errorView = buildErrorView();
}
return errorView;
} }
return errorView;
}
public function new(view:IGroupView) { public function new(view:IGroupView) {
this.view = view; this.view = view;
} }
private function buildLoadingView():IView<Dynamic> { private function buildLoadingView():IView<Dynamic> {
var view = new LabelView(); var view = new LabelView();
view.geometry.position = ABSOLUTE; view.geometry.position = ABSOLUTE;
view.geometry.hAlign = CENTER; view.geometry.hAlign = CENTER;
view.geometry.vAlign = MIDDLE; view.geometry.vAlign = MIDDLE;
view.text = "Loading..."; view.text = "Loading...";
return view; return view;
} }
private function buildErrorView():IView<Dynamic> { private function buildErrorView():IView<Dynamic> {
var view = new TextView(); var view = new TextView();
view.geometry.position = ABSOLUTE; view.geometry.position = ABSOLUTE;
view.geometry.hAlign = CENTER; view.geometry.hAlign = CENTER;
view.geometry.vAlign = MIDDLE; view.geometry.vAlign = MIDDLE;
view.geometry.stretch = true; view.geometry.stretch = true;
view.style = "text.error"; view.style = "text.error";
return view; return view;
} }
public function reset():Void { public function reset():Void {
overlay = null; overlay = null;
} }
} }

View File

@@ -7,76 +7,75 @@ import hw.view.form.ToggleButtonView;
import hw.view.layout.TailLayout; import hw.view.layout.TailLayout;
enum PaginatorAction { enum PaginatorAction {
START; START;
JUMP(distance:Int); JUMP(distance:Int);
INDEX(index:Int); INDEX(index:Int);
END; END;
} }
class PaginatorButtonView extends ToggleButtonView { class PaginatorButtonView extends ToggleButtonView {
public var action(default, set):PaginatorAction; public var action(default, set):PaginatorAction;
private function set_action(value:PaginatorAction):PaginatorAction { private function set_action(value:PaginatorAction):PaginatorAction {
action = value; action = value;
text = switch action { text = switch action {
case INDEX(index): Std.string(index); case INDEX(index): Std.string(index);
case x: Std.string(x); case x: Std.string(x);
}
return action;
} }
return action;
}
public static function factory(index:Int, action:PaginatorAction):PaginatorButtonView { public static function factory(index:Int, action:PaginatorAction):PaginatorButtonView {
var result = new PaginatorButtonView(); var result = new PaginatorButtonView();
result.action = action; result.action = action;
return result; return result;
} }
} }
class PaginatorView extends DataView<PaginatorAction, PaginatorButtonView> { class PaginatorView extends DataView<PaginatorAction, PaginatorButtonView> {
public var page(default, set):DataPage<Dynamic>;
public var onPageSelect(default, null):Signal<Int>;
public var page(default, set):DataPage<Dynamic>; private function set_page(value:DataPage<Dynamic>):DataPage<Dynamic> {
public var onPageSelect(default, null):Signal<Int>; page = value;
refresh();
return page;
}
private function set_page(value:DataPage<Dynamic>):DataPage<Dynamic> { public function new() {
page = value; super(new TailLayout());
refresh(); layout.margin = 3;
return page; onPageSelect = new Signal();
factory = PaginatorButtonView.factory;
onDataSelect.connect(onAction);
}
private function onAction(action:PaginatorAction):Void {
onPageSelect.emit(switch action {
case START: 0;
case INDEX(index): index;
case JUMP(distance): page.page.index + distance;
case END: Std.int((page.total - 1) / page.page.count);
});
}
private function refresh():Void {
var total:Int = Std.int((page.total - 1) / page.page.count);
var start:Int = Std.int(Math.max(page.page.index - 2, 0));
var end:Int = Std.int(Math.min(page.page.index + 2, total));
var data = [for (index in start...end + 1) INDEX(index)];
if (start > 0) {
data.unshift(START);
} }
if (end < total) {
public function new() { data.push(END);
super(new TailLayout());
layout.margin = 3;
onPageSelect = new Signal();
factory = PaginatorButtonView.factory;
onDataSelect.connect(onAction);
} }
this.data = data;
private function onAction(action:PaginatorAction):Void { for (button in dataViews) {
onPageSelect.emit(switch action { button.on = switch button.action {
case START: 0; case INDEX(index): index == page.page.index;
case INDEX(index): index; case x: false;
case JUMP(distance): page.page.index + distance; }
case END: Std.int((page.total - 1) / page.page.count);
});
}
private function refresh():Void {
var total:Int = Std.int((page.total - 1) / page.page.count);
var start:Int = Std.int(Math.max(page.page.index - 2, 0));
var end:Int = Std.int(Math.min(page.page.index + 2, total));
var data = [for (index in start...end + 1) INDEX(index)];
if (start > 0) {
data.unshift(START);
}
if (end < total) {
data.push(END);
}
this.data = data;
for (button in dataViews) {
button.on = switch button.action {
case INDEX(index): index == page.page.index;
case x: false;
}
}
} }
}
} }

View File

@@ -4,7 +4,7 @@ import com.hurlant.crypto.prng.Random;
import com.hurlant.crypto.extra.UUID; import com.hurlant.crypto.extra.UUID;
class IdUtil { class IdUtil {
public static function generate():String { public static function generate():String {
return UUID.generateRandom(new Random()).toString().split("-").shift(); return UUID.generateRandom(new Random()).toString().split("-").shift();
} }
} }

View File

@@ -9,28 +9,19 @@ import ru.m.puzzlez.proto.event.GameEvent;
import ru.m.puzzlez.proto.game.GameState; import ru.m.puzzlez.proto.game.GameState;
class EventUtil { class EventUtil {
public static function start(state:GameState, resume:Bool):GameEvent {
return new GameEvent().setStart(new GameStart().setState(state).setResume(resume));
}
public static function start(state:GameState, resume: Bool):GameEvent { public static function complete():GameEvent {
return new GameEvent().setStart(new GameStart() return new GameEvent().setComplete(new GameComplete());
.setState(state) }
.setResume(resume)
);
}
public static function complete():GameEvent { public static function action(action:GameAction):GameEvent {
return new GameEvent().setComplete(new GameComplete()); return new GameEvent().setAction(action);
} }
public static function action(action:GameAction):GameEvent { public static function change(part:Part):GameEvent {
return new GameEvent().setAction(action); return new GameEvent().setChange(new GameChange().setPartId(part.id).setLocation(part.location).setPosition(part.position).setPlayerId(part.playerId));
} }
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

@@ -12,109 +12,108 @@ import ru.m.puzzlez.proto.game.PartLocation;
import ru.m.puzzlez.wrap.PointExt; 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;
public var events(default, null):Signal<GameEvent>; public var events(default, null):Signal<GameEvent>;
private var partsById:Map<Int, Part>; private var partsById:Map<Int, Part>;
public function new(state:GameState) { public function new(state:GameState) {
this.state = state; this.state = state;
partsById = new Map(); partsById = new Map();
for (part in state.parts) { for (part in state.parts) {
partsById[part.id] = part; partsById[part.id] = part;
}
events = new Signal();
events.connect(onGameEvent);
} }
events = new Signal();
events.connect(onGameEvent);
}
public function action(action:GameAction):Void { public function action(action:GameAction):Void {
events.emit(EventUtil.action(action)); events.emit(EventUtil.action(action));
}
public function start():Void {
switch state.status {
case GameStatus.READY:
shuffle();
state.status = GameStatus.STARTED;
events.emit(EventUtil.start(state, false));
case _:
events.emit(EventUtil.start(state, true));
} }
}
public function start():Void { private function shuffle():Void {
switch state.status { for (part in state.parts) {
case GameStatus.READY: switch part.location {
shuffle(); case PartLocation.TABLE:
state.status = GameStatus.STARTED; var bound = part.rect.width * 0.25;
events.emit(EventUtil.start(state, false)); 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);
part.position = new PointExt(x, y);
case _:
}
}
}
private static function distance(a:PointExt, b:PointExt):Float {
var diff:Point = a.subtract(b);
return Math.abs(diff.x) + Math.abs(diff.y);
}
private function checkIsComplete():Bool {
for (part in partsById) {
switch part.location {
case PartLocation.IMAGE:
case _:
return false;
}
}
return true;
}
private function onGameEvent(event:GameEvent):Void {
if (event.hasAction()) {
var part:Part = partsById[event.action.partId];
switch event.action.action {
case Action.TAKE:
switch part.location {
case PartLocation.TABLE:
part.location = PartLocation.HAND;
part.playerId = event.action.playerId;
events.emit(EventUtil.change(part));
case _: case _:
events.emit(EventUtil.start(state, true)); }
} case Action.MOVE:
} switch part.location {
case PartLocation.HAND if (event.action.playerId == part.playerId):
private function shuffle():Void { part.position = event.action.position;
for (part in state.parts) { events.emit(EventUtil.change(part));
switch part.location { case _:
case PartLocation.TABLE: }
var bound = part.rect.width * 0.25; case Action.PUT:
var x = bound + Math.random() * (state.preset.tableRect.width - part.rect.width - bound * 2); part.playerId = null;
var y = bound + Math.random() * (state.preset.tableRect.height - part.rect.height - bound * 2); var target = new PointExt(state.preset.imageRect.x,
part.position = new PointExt(x, y); state.preset.imageRect.y).add(new PointExt(part.point.x * part.rect.width, part.point.y * part.rect.height));
case _: 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));
}
}
} }
}
private static function distance(a:PointExt, b:PointExt):Float { public function stop():Void {}
var diff:Point = a.subtract(b);
return Math.abs(diff.x) + Math.abs(diff.y);
}
private function checkIsComplete():Bool { public function dispose():Void {
for (part in partsById) { events.dispose();
switch part.location { }
case PartLocation.IMAGE:
case _: return false;
}
}
return true;
}
private function onGameEvent(event:GameEvent):Void {
if (event.hasAction()) {
var part:Part = partsById[event.action.partId];
switch event.action.action {
case Action.TAKE:
switch part.location {
case PartLocation.TABLE:
part.location = PartLocation.HAND;
part.playerId = event.action.playerId;
events.emit(EventUtil.change(part));
case _:
}
case Action.MOVE:
switch part.location {
case PartLocation.HAND if (event.action.playerId == part.playerId):
part.position = event.action.position;
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));
}
}
}
}
public function stop():Void {
}
public function dispose():Void {
events.dispose();
}
} }

View File

@@ -15,139 +15,125 @@ import ru.m.puzzlez.proto.game.PartBounds;
import ru.m.puzzlez.proto.game.PartLocation; import ru.m.puzzlez.proto.game.PartLocation;
class BoundsMap { class BoundsMap {
private var grid:IntPoint;
private var bounds:Array<PartBound>;
private var grid:IntPoint; public function new(grid:IntPoint) {
private var bounds:Array<PartBound>; this.grid = grid;
bounds = [];
public function new(grid:IntPoint) { for (x in 0...grid.x + 1) {
this.grid = grid; for (y in 0...grid.y + 1) {
bounds = []; bounds.push(buildPartBound());
for (x in 0...grid.x + 1) { bounds.push(buildPartBound());
for (y in 0...grid.y + 1) { }
bounds.push(buildPartBound());
bounds.push(buildPartBound());
}
}
} }
}
public function buildBound():Int { 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 new PartBound().setSpike(buildBound()).setSide(buildBound()); return new PartBound().setSpike(buildBound()).setSide(buildBound());
} }
public function buildNonePartBound():PartBound { public function buildNonePartBound():PartBound {
return new PartBound().setSpike(BoundType.NONE).setSide(BoundType.NONE); return new PartBound().setSpike(BoundType.NONE).setSide(BoundType.NONE);
} }
public function revert(type:Int):Int { public function revert(type:Int):Int {
return switch type { return switch type {
case BoundType.IN: BoundType.OUT; case BoundType.IN: BoundType.OUT;
case BoundType.OUT: BoundType.IN; case BoundType.OUT: BoundType.IN;
case _: BoundType.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.x + 1) * (y * 2); case TOP: x + (grid.x + 1) * (y * 2);
case LEFT: x + (grid.x + 1) * (y + 1); case LEFT: x + (grid.x + 1) * (y + 1);
case RIGHT: x + (grid.x + 1) * (y + 1) + 1; case RIGHT: x + (grid.x + 1) * (y + 1) + 1;
case BOTTOM: x + (grid.x + 1) * (y * 2) + (grid.x + 1) * 2; case BOTTOM: x + (grid.x + 1) * (y * 2) + (grid.x + 1) * 2;
}
return switch side {
case TOP | LEFT: new PartBound().setSpike(revert(bounds[index].spike)).setSide(revert(bounds[index].side));
case _: bounds[index];
}
} }
return switch side {
case TOP | LEFT: new PartBound().setSpike(revert(bounds[index].spike)).setSide(revert(bounds[index].side));
case _: bounds[index];
}
}
} }
class GameUtil { class GameUtil {
private static inline var MAX_SIZE = 20;
private static inline var MAX_SIZE = 20; private static function normilizeSize(size:Int, maxValue:Int = MAX_SIZE):Int {
return Math.isNaN(size) ? maxValue : size < 1 ? 1 : size > maxValue ? maxValue : size;
}
private static function normilizeSize(size:Int, maxValue:Int = MAX_SIZE):Int { public static function buildPreset(image:ImageId, width:Int = 8, height:Int = MAX_SIZE):GamePreset {
return Math.isNaN(size) ? maxValue : size < 1 ? 1 : size > maxValue ? maxValue : size; width = normilizeSize(width);
} height = normilizeSize(height);
var offsetX = 500;
var offsetY = 200;
var imageSize = 1024;
var s = width / height;
var imageRect = new Rectangle().setX(offsetX).setY(offsetY).setWidth(imageSize).setHeight(imageSize / s);
var tableRect = new Rectangle().setX(0)
.setY(0)
.setWidth(imageRect.width + offsetX * 2)
.setHeight(imageRect.height + offsetY * 2);
return new GamePreset().setImage(image)
.setGrid(new IntPoint().setX(width).setY(height))
.setTableRect(tableRect)
.setImageRect(imageRect);
}
public static function buildPreset(image: ImageId, width:Int = 8, height:Int = MAX_SIZE):GamePreset { public static function buildState(preset:GamePreset):GameState {
width = normilizeSize(width); var parts:Array<Part> = [];
height = normilizeSize(height); var partWidth = preset.imageRect.width / preset.grid.x;
var offsetX = 500; var partHeight = preset.imageRect.height / preset.grid.y;
var offsetY = 200; var position = new Point().setX(preset.imageRect.x).setY(preset.imageRect.y);
var imageSize = 1024; var boundsMap = new BoundsMap(preset.grid);
var s = width / height; for (y in 0...preset.grid.y) {
var imageRect = new Rectangle() for (x in 0...preset.grid.x) {
.setX(offsetX) var bounds:PartBounds = new PartBounds().setLeft(boundsMap.getBound(x, y, LEFT))
.setY(offsetY) .setRight(boundsMap.getBound(x, y, RIGHT))
.setWidth(imageSize) .setTop(boundsMap.getBound(x, y, TOP))
.setHeight(imageSize / s); .setBottom(boundsMap.getBound(x, y, BOTTOM));
var tableRect = new Rectangle() if (x == 0) {
.setX(0) bounds.left = boundsMap.buildNonePartBound();
.setY(0)
.setWidth(imageRect.width + offsetX * 2)
.setHeight(imageRect.height + offsetY * 2);
return new GamePreset()
.setImage(image)
.setGrid(new IntPoint().setX(width).setY(height))
.setTableRect(tableRect)
.setImageRect(imageRect);
}
public static function buildState(preset:GamePreset):GameState {
var parts:Array<Part> = [];
var partWidth = preset.imageRect.width / preset.grid.x;
var partHeight = preset.imageRect.height / preset.grid.y;
var position = new Point().setX(preset.imageRect.x).setY(preset.imageRect.y);
var boundsMap = new BoundsMap(preset.grid);
for (y in 0...preset.grid.y) {
for (x in 0...preset.grid.x) {
var bounds:PartBounds = new PartBounds()
.setLeft(boundsMap.getBound(x, y, LEFT))
.setRight(boundsMap.getBound(x, y, RIGHT))
.setTop(boundsMap.getBound(x, y, TOP))
.setBottom(boundsMap.getBound(x, y, BOTTOM));
if (x == 0) {
bounds.left = boundsMap.buildNonePartBound();
}
if (y == 0) {
bounds.top = boundsMap.buildNonePartBound();
}
if (x == preset.grid.x - 1) {
bounds.right = boundsMap.buildNonePartBound();
}
if (y == preset.grid.y - 1) {
bounds.bottom = boundsMap.buildNonePartBound();
}
var id = (x << 16) + y;
var position = new Point().setX(position.x + x * partWidth).setY(position.y + y * partHeight);
parts.push(new Part()
.setId(id)
.setPoint(new IntPoint().setX(x).setY(y))
.setLocation(PartLocation.TABLE)
.setPosition(position)
.setBounds(bounds)
.setRect(new Rectangle().setX(0).setY(0).setWidth(partWidth).setHeight(partHeight))
);
}
} }
return new GameState() if (y == 0) {
.setId(IdUtil.generate()) bounds.top = boundsMap.buildNonePartBound();
.setStatus(GameStatus.READY)
.setPreset(preset)
.setParts(parts);
}
public static function calcProgress(state:GameState):{complete:Int, total:Int} {
var complete = 0;
for (part in state.parts) {
switch part.location {
case PartLocation.IMAGE: complete++;
case _:
}
} }
return {complete:complete, total:state.parts.length}; if (x == preset.grid.x - 1) {
bounds.right = boundsMap.buildNonePartBound();
}
if (y == preset.grid.y - 1) {
bounds.bottom = boundsMap.buildNonePartBound();
}
var id = (x << 16) + y;
var position = new Point().setX(position.x + x * partWidth).setY(position.y + y * partHeight);
parts.push(new Part().setId(id)
.setPoint(new IntPoint().setX(x).setY(y))
.setLocation(PartLocation.TABLE)
.setPosition(position)
.setBounds(bounds)
.setRect(new Rectangle().setX(0).setY(0).setWidth(partWidth).setHeight(partHeight)));
}
} }
return new GameState().setId(IdUtil.generate()).setStatus(GameStatus.READY).setPreset(preset).setParts(parts);
}
public static function calcProgress(state:GameState):{complete:Int, total:Int} {
var complete = 0;
for (part in state.parts) {
switch part.location {
case PartLocation.IMAGE:
complete++;
case _:
}
}
return {complete: complete, total: state.parts.length};
}
} }

View File

@@ -6,14 +6,14 @@ import ru.m.puzzlez.proto.event.GameEvent;
import ru.m.puzzlez.proto.game.GameState; import ru.m.puzzlez.proto.game.GameState;
interface IGame { interface IGame {
public var state(default, null):GameState; public var state(default, null):GameState;
public var events(default, null):Signal<GameEvent>; public var events(default, null):Signal<GameEvent>;
public function action(action:GameAction):Void; public function action(action:GameAction):Void;
public function start():Void; public function start():Void;
public function stop():Void; public function stop():Void;
public function dispose():Void; public function dispose():Void;
} }

View File

@@ -1,8 +1,8 @@
package ru.m.puzzlez.image; package ru.m.puzzlez.image;
enum Side { enum Side {
TOP; TOP;
LEFT; LEFT;
RIGHT; RIGHT;
BOTTOM; BOTTOM;
} }

View File

@@ -6,36 +6,25 @@ import flash.geom.Point as FlashPoint;
#end #end
abstract PointExt(Point) from Point to Point { 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 new(x: Float = 0, y: Float = 0) { public function clone():PointExt {
this = new Point() return new Point().setX(this.x).setY(this.y);
.setX(x) }
.setY(y);
}
public function clone():PointExt { public function add(point:Point):PointExt {
return new Point() return new Point().setX(this.x + point.x).setY(this.y + point.y);
.setX(this.x) }
.setY(this.y);
}
public function add(point:Point):PointExt { public function subtract(point:Point):PointExt {
return new Point() return new Point().setX(this.x - point.x).setY(this.y - point.y);
.setX(this.x + point.x) }
.setY(this.y + point.y);
}
public function subtract(point:Point):PointExt { #if app
return new Point() @:from public static function fromFlashPoint(point:FlashPoint):PointExt {
.setX(this.x - point.x) return new Point().setX(point.x).setY(point.y);
.setY(this.y - point.y); }
} #end
#if app
@:from public static function fromFlashPoint(point:FlashPoint):PointExt {
return new Point()
.setX(point.x)
.setY(point.y);
}
#end
} }

View File

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

View File

@@ -24,122 +24,120 @@ import ru.m.puzzlez.proto.pack.Response;
import sys.net.Socket; import sys.net.Socket;
class GameSession extends ProtoSession<Response, Request> { class GameSession extends ProtoSession<Response, Request> {
private static inline var TAG = "Session"; private static inline var TAG = "Session";
public static var gamesById(default, null):Map<String, Game> = new Map(); public static var gamesById(default, null):Map<String, Game> = new Map();
public static var sessionsById(default, null):Map<Int, GameSession> = new Map(); public static var sessionsById(default, null):Map<Int, GameSession> = new Map();
public var user(default, null):User; public var user(default, null):User;
public var game(default, null):Game; public var game(default, null):Game;
private var subscribed:Bool; private var subscribed:Bool;
private var tag(get, never):String; private var tag(get, never):String;
private function get_tag():String { private function get_tag():String {
return '[${id}|${user == null ? '-' : user.uuid}|${game == null ? '-' : game.state.id}]'; return '[${id}|${user == null ? '-' : user.uuid}|${game == null ? '-' : game.state.id}]';
}
public function new(socket:Socket) {
super(socket, Request);
sessionsById.set(id, this);
}
private function sendError(code:Int, message:String):Void {
send(new Response().setError(new ErrorResponse().setCode(code).setMessage(message)));
}
override public function send(packet:Response):Void {
#if proto_debug L.d(TAG, '$tag send: ${packet}'); #end
try {
super.send(packet);
} catch (error:Dynamic) {
L.e(TAG, '$tag send ', error);
} }
}
public function new(socket:Socket) { private function auth(auth:AuthRequest):Void {
super(socket, Request); user = auth.hasUser() ? auth.user : new User().setUuid(IdUtil.generate()).setName('Anonim #${IdUtil.generate()}');
sessionsById.set(id, this); send(new Response().setAuth(new AuthResponse().setUser(user)));
send(new Response().setNotification(new NotificationResponse().setGames(Lambda.count(gamesById))));
}
private function list(list:GameListRequest):Void {
var games = Lambda.array(gamesById);
var pageGames = games.slice(list.page * list.count, list.count);
send(new Response().setList(new GameListResponse().setPage(list.page)
.setCount(list.count)
.setTotal(games.length)
.setGames(pageGames.map(game -> game.state))));
}
private function create(create:GameCreateRequest):Void {
var game = new Game(GameUtil.buildState(create.preset).setOnline(true));
game.start();
gamesById.set(game.state.id, game);
join(game.state.id);
for (session in sessionsById) {
session.send(new Response().setNotification(new NotificationResponse().setGames(Lambda.count(gamesById))));
} }
}
private function sendError(code:Int, message:String):Void { private function join(gameId:String):Void {
send(new Response().setError(new ErrorResponse().setCode(code).setMessage(message))); game = gamesById.get(gameId);
} game.events.connect(onGameEvent);
game.events.emit(new GameEvent().setPlayer(new GamePlayer().setPlayer(user).setAction(GamePlayerAction.JOIN)));
send(new Response().setJoin(new GameJoinResponse().setGame(game.state)));
}
override public function send(packet:Response):Void { private function leave():Void {
#if proto_debug L.d(TAG, '$tag send: ${packet}'); #end game.events.disconnect(onGameEvent);
try { game.events.emit(new GameEvent().setPlayer(new GamePlayer().setPlayer(user).setAction(GamePlayerAction.LEAVE)));
super.send(packet); send(new Response().setLeave(new GameLeaveResponse()));
} catch (error:Dynamic) { }
L.e(TAG, '$tag send ', error);
private function action(action:GameAction):Void {
game.events.emit(new GameEvent().setAction(action));
}
override private function onRequest(request:Request):Void {
#if proto_debug L.d(TAG, '$tag onRequest: ${request}'); #end
try {
if (!request.hasAuth() && user == null) {
throw "Not Authorized";
}
if (request.hasAuth()) {
auth(request.auth);
} else if (request.hasList()) {
list(request.list);
} else if (request.hasCreate()) {
create(request.create);
} else if (request.hasJoin()) {
join(request.join.gameId);
} else if (request.hasLeave()) {
leave();
} else if (request.hasAction()) {
for (action in request.action.actions) {
this.action(action);
} }
}
} catch (error:Dynamic) {
L.e(TAG, '$tag onRequest ', error);
sendError(500, LoggerUtil.printError(error));
} }
}
private function auth(auth:AuthRequest):Void { private function onGameEvent(event:GameEvent):Void {
user = auth.hasUser() ? auth.user : new User().setUuid(IdUtil.generate()).setName('Anonim #${IdUtil.generate()}'); send(new Response().setEvent(new GameEventResponse().setEvents([event])));
send(new Response().setAuth(new AuthResponse().setUser(user))); }
send(new Response().setNotification(new NotificationResponse().setGames(Lambda.count(gamesById))));
}
private function list(list:GameListRequest):Void { override public function disconnect():Void {
var games = Lambda.array(gamesById); L.d(TAG, '$tag disconnect');
var pageGames = games.slice(list.page * list.count, list.count); if (game != null) {
send(new Response().setList(new GameListResponse() leave();
.setPage(list.page) game = null;
.setCount(list.count)
.setTotal(games.length)
.setGames(pageGames.map(game -> game.state))
));
}
private function create(create:GameCreateRequest):Void {
var game = new Game(GameUtil.buildState(create.preset).setOnline(true));
game.start();
gamesById.set(game.state.id, game);
join(game.state.id);
for (session in sessionsById) {
session.send(new Response().setNotification(new NotificationResponse().setGames(Lambda.count(gamesById))));
}
}
private function join(gameId:String):Void {
game = gamesById.get(gameId);
game.events.connect(onGameEvent);
game.events.emit(new GameEvent().setPlayer(new GamePlayer().setPlayer(user).setAction(GamePlayerAction.JOIN)));
send(new Response().setJoin(new GameJoinResponse().setGame(game.state)));
}
private function leave():Void {
game.events.disconnect(onGameEvent);
game.events.emit(new GameEvent().setPlayer(new GamePlayer().setPlayer(user).setAction(GamePlayerAction.LEAVE)));
send(new Response().setLeave(new GameLeaveResponse()));
}
private function action(action:GameAction):Void {
game.events.emit(new GameEvent().setAction(action));
}
override private function onRequest(request:Request):Void {
#if proto_debug L.d(TAG, '$tag onRequest: ${request}'); #end
try {
if (!request.hasAuth() && user == null) {
throw "Not Authorized";
}
if (request.hasAuth()) {
auth(request.auth);
} else if (request.hasList()) {
list(request.list);
} else if (request.hasCreate()) {
create(request.create);
} else if (request.hasJoin()) {
join(request.join.gameId);
} else if (request.hasLeave()) {
leave();
} else if (request.hasAction()) {
for (action in request.action.actions) {
this.action(action);
}
}
} catch (error:Dynamic) {
L.e(TAG, '$tag onRequest ', error);
sendError(500, LoggerUtil.printError(error));
}
}
private function onGameEvent(event:GameEvent):Void {
send(new Response().setEvent(new GameEventResponse().setEvents([event])));
}
override public function disconnect():Void {
L.d(TAG, '$tag disconnect');
if (game != null) {
leave();
game = null;
}
user = null;
sessionsById.remove(id);
super.disconnect();
} }
user = null;
sessionsById.remove(id);
super.disconnect();
}
} }

View File

@@ -8,41 +8,40 @@ import haxe.io.Bytes;
typedef Message = Bytes; typedef Message = Bytes;
typedef ClientMessage<M> = { typedef ClientMessage<M> = {
var msg:M; var msg:M;
var bytes:Int; var bytes:Int;
} }
class PuzzlezServer extends ThreadServer<GameSession, Message> { class PuzzlezServer extends ThreadServer<GameSession, Message> {
private static inline var TAG = 'Server';
private static inline var TAG = 'Server'; override public function clientConnected(socket:Socket):GameSession {
var session = new GameSession(socket);
L.d(TAG, 'Client connected');
return session;
}
override public function clientConnected(socket:Socket):GameSession { override public function clientDisconnected(session:GameSession) {
var session = new GameSession(socket); L.d(TAG, 'Client disconnected');
L.d(TAG, 'Client connected'); session.disconnect();
return session; }
}
override public function clientDisconnected(session:GameSession) { override public function readClientMessage(session:GameSession, buf:Bytes, pos:Int, len:Int):ClientMessage<Message> {
L.d(TAG, 'Client disconnected'); // L.d(TAG, 'Client message: ${buf}');
session.disconnect(); return {msg: buf.sub(pos, len), bytes: len};
} }
override public function readClientMessage(session:GameSession, buf:Bytes, pos:Int, len:Int): ClientMessage<Message> { override public function clientMessage(session:GameSession, message:Message) {
//L.d(TAG, 'Client message: ${buf}'); session.pushData(message);
return {msg: buf.sub(pos, len), bytes: len}; }
}
override public function clientMessage(session:GameSession, message:Message) { public static function main():Void {
session.pushData(message); L.push(new TraceLogger());
} L.i(TAG, 'Build: ${CompilationOption.get("build")}');
var host:String = Sys.args().length > 0 ? Sys.args()[0] : "0.0.0.0";
public static function main():Void { var port:Int = Sys.args().length > 1 ? Std.parseInt(Sys.args()[1]) : 5000;
L.push(new TraceLogger()); var wserver = new PuzzlezServer();
L.i(TAG, 'Build: ${CompilationOption.get("build")}'); L.i(TAG, 'Start on ${host}:${port}');
var host:String = Sys.args().length > 0 ? Sys.args()[0] : "0.0.0.0"; wserver.run(host, port);
var port:Int = Sys.args().length > 1 ? Std.parseInt(Sys.args()[1]) : 5000; }
var wserver = new PuzzlezServer();
L.i(TAG, 'Start on ${host}:${port}');
wserver.run(host, port);
}
} }

View File

@@ -1,114 +1,134 @@
const gulp = require('gulp'); const gulp = require("gulp");
const fs = require('fs'); const fs = require("fs");
const fse = require('fs-extra'); const fse = require("fs-extra");
const path = require('path'); const path = require("path");
const through = require('through2'); const through = require("through2");
class Package { class Package {
constructor(platform, type, version, versionInt) {
this.platform = platform;
this.type = type;
this.version = version;
this.versionInt = versionInt;
}
constructor(platform, type, version, versionInt) { get key() {
this.platform = platform; return [this.platform, this.type].join(":");
this.type = type; }
this.version = version;
this.versionInt = versionInt;
}
get key() { static getPlatform(filename) {
return [this.platform, this.type].join(':'); for (const [platform, r] of [
["android", /^.*?\.apk$/],
["linux", /^.*?\.deb$/],
["linux", /^.*?linux\.tar\.gz$/],
["windows", /^.*?win\.zip$/],
["windows", /^.*?\.exe$/],
]) {
if (r.test(filename)) {
return platform;
}
} }
return null;
}
static getPlatform(filename) { static getType(filename) {
for (const [platform, r] of [ for (const [type, r] of [
['android', /^.*?\.apk$/], ["apk", /^.*?\.apk$/],
['linux', /^.*?\.deb$/], ["deb", /^.*?\.deb$/],
['linux', /^.*?linux\.tar\.gz$/], ["archive", /^.*?\.tar\.gz$/],
['windows', /^.*?win\.zip$/], ["archive", /^.*?\.zip$/],
['windows', /^.*?\.exe$/], ["exe", /^.*?\.exe$/],
]) { ]) {
if (r.test(filename)) { if (r.test(filename)) {
return platform; return type;
} }
}
return null;
} }
return null;
}
static getType(filename) { static getVersion(filename) {
for (const [type, r] of [ const m = /(\d+)\.(\d+)\.(\d+)/.exec(filename);
['apk', /^.*?\.apk$/], if (m) {
['deb', /^.*?\.deb$/], return [
['archive', /^.*?\.tar\.gz$/], m[0],
['archive', /^.*?\.zip$/], parseInt(m[1]) * 10000 + parseInt(m[2]) * 100 + parseInt(m[3]),
['exe', /^.*?\.exe$/], ];
]) {
if (r.test(filename)) {
return type;
}
}
return null;
} }
return null;
}
static getVersion(filename) { static parseFilename(filename) {
const m = /(\d+)\.(\d+)\.(\d+)/.exec(filename); const platform = this.getPlatform(filename);
if (m) { const type = this.getType(filename);
return [m[0], parseInt(m[1]) * 10000 + parseInt(m[2]) * 100 + parseInt(m[3])]; const version = this.getVersion(filename);
} if (platform && type && version) {
return null; return new Package(platform, type, version[0], version[1]);
}
static parseFilename(filename) {
const platform = this.getPlatform(filename);
const type = this.getType(filename);
const version = this.getVersion(filename);
if (platform && type && version) {
return new Package(platform, type, version[0], version[1]);
}
return null;
} }
return null;
}
} }
module.exports = (name, version, publishDir, publishUrl) => function publish(cb) { module.exports = (name, version, publishDir, publishUrl) =>
function publish(cb) {
const packages = {}; const packages = {};
fse.ensureDirSync(publishDir); fse.ensureDirSync(publishDir);
return gulp.series([ return gulp.series([
function copy() { function copy() {
return gulp.src('target/app/@(android|archive|debian|installer)/*') return gulp
.pipe(through.obj(function (file, enc, cb) { .src("target/app/@(android|archive|debian|installer)/*")
const pack = Package.parseFilename(file.path); .pipe(
if (pack) { through.obj(function (file, enc, cb) {
this.push(file); const pack = Package.parseFilename(file.path);
} if (pack) {
cb(null); this.push(file);
})) }
.pipe(gulp.dest(publishDir)) cb(null);
}, })
function generate() { )
return gulp.src(`${publishDir}/*/*`) .pipe(gulp.dest(publishDir));
.pipe(through.obj(function (file, enc, cb) { },
const basepath = file.path.replace(file.base + path.sep, ''); function generate() {
const pack = Package.parseFilename(file.path); return gulp
if (pack) { .src(`${publishDir}/*/*`)
if (!packages[pack.key] || packages[pack.key].versionInt < pack.versionInt) { .pipe(
packages[pack.key] = { through.obj(function (file, enc, cb) {
platform: pack.platform, const basepath = file.path.replace(file.base + path.sep, "");
type: pack.type, const pack = Package.parseFilename(file.path);
version: pack.version, if (pack) {
versionInt: pack.versionInt, if (
path: basepath, !packages[pack.key] ||
filename: basepath.split(path.sep).pop(), packages[pack.key].versionInt < pack.versionInt
url: `${publishUrl}/${basepath.replace(path.sep, "/")}`, ) {
} packages[pack.key] = {
} platform: pack.platform,
} type: pack.type,
cb(null); version: pack.version,
})).on('end', function () { versionInt: pack.versionInt,
fs.writeFileSync(path.join(publishDir, 'packages.json'), JSON.stringify({ path: basepath,
name: name, filename: basepath.split(path.sep).pop(),
version: version, url: `${publishUrl}/${basepath.replace(path.sep, "/")}`,
packages: Object.values(packages), };
}, null, 4)); }
}) }
} cb(null);
})
)
.on("end", function () {
fs.writeFileSync(
path.join(publishDir, "packages.json"),
JSON.stringify(
{
name: name,
version: version,
packages: Object.values(packages),
},
null,
4
)
);
});
},
])(cb); ])(cb);
}; };
module.exports.Package = Package; module.exports.Package = Package;