diff --git a/assembly/dice.ts b/assembly/dice.ts index b669104..73c4edf 100644 --- a/assembly/dice.ts +++ b/assembly/dice.ts @@ -1,5 +1,5 @@ -import { JSONDecoder } from "../node_modules/assemblyscript-json/assembly/decoder"; -import { JSONEncoder } from "../node_modules/assemblyscript-json/assembly/encoder"; +import {JSONEncoder} from "../node_modules/assemblyscript-json/assembly/encoder"; +import {ErrorResponse, GetBalanceResponse, JoinResponse, Response, RollResponse} from "./response"; const PLAYERS_MAX_COUNT: usize = 1024; const SEED: u64 = 12345678; @@ -7,8 +7,66 @@ const SEED: u64 = 12345678; const INIT_ACCOUNT_BALANCE: u64 = 100; // if win, player receives bet_amount * PAYOUT_RATE money const PAYOUT_RATE: u64 = 5; +const DICE_LINE_COUNT: u8 = 6; -export function handler(input: string): string { +export class GameManager { - return ""; + registeredPlayers: u64 = 0; + playerIds: u64[] = []; + playerBalance: Map = new Map(); + encoder: JSONEncoder = new JSONEncoder(); + + constructor() { + NativeMath.seedRandom(SEED); + } + + join(): Response { + // delete the oldest player, if maximum players reach + if (this.playerIds.length >= PLAYERS_MAX_COUNT) { + let lastPlayer = this.playerIds.pop(); + this.playerBalance.delete(lastPlayer); + } + + this.playerIds.push(this.registeredPlayers); + this.playerBalance.set(this.registeredPlayers, INIT_ACCOUNT_BALANCE); + + let response = new JoinResponse(this.registeredPlayers); + + this.registeredPlayers += 1; + + return response; + } + + roll(playerId: u64, betPlacement: u8, betSize: u32): Response { + + if (betPlacement > DICE_LINE_COUNT) { + return new ErrorResponse("Incorrect placement, please choose number from 1 to 6") + } + + if (!this.playerBalance.has(playerId)) { + return new ErrorResponse("There is no player with such id: " + playerId.toString()) + } + + let balance = this.playerBalance.get(playerId); + + if (betSize > balance) { + return new ErrorResponse(`Player hasn't enough money: player's current balance is ${balance.toString()} while the bet is ${betSize.toString()}`) + } + + let outcome = ((NativeMath.random() * 1000000000) % DICE_LINE_COUNT - 1) as u8; + + if (betPlacement === outcome) { + balance = balance + (betSize * PAYOUT_RATE); + } else { + balance = balance - betSize; + } + + this.playerBalance.set(playerId, balance); + + return new RollResponse(outcome, balance); + } + + getBalance(playerId: u64): Response { + return new GetBalanceResponse(1); + } } diff --git a/assembly/game_handler.ts b/assembly/game_handler.ts new file mode 100644 index 0000000..50e50cc --- /dev/null +++ b/assembly/game_handler.ts @@ -0,0 +1,25 @@ +import {decode, GetBalanceRequest, JoinRequest, RollRequest, UnknownRequest} from "./request"; +import {ErrorResponse} from "./response"; +import {GameManager} from "./dice"; + +let gameManager = new GameManager(); + +// returns string, because serialization to a byte array is not compatible with our invoke handlers +export function handler(requestBytes: Uint8Array): string { + + let request = decode(requestBytes); + + if (request instanceof JoinRequest) { + return gameManager.join().serialize(); + } else if (request instanceof RollRequest) { + return gameManager.roll(request.playerId, request.betPlacement, request.betSize).serialize(); + } else if (request instanceof GetBalanceRequest) { + return gameManager.getBalance(request.playerId).serialize(); + } else if (request instanceof UnknownRequest) { + return new ErrorResponse("").serialize(); + } else { + + } + + return ""; +} diff --git a/assembly/index.ts b/assembly/index.ts index 0966a32..b9ce111 100644 --- a/assembly/index.ts +++ b/assembly/index.ts @@ -1,9 +1,9 @@ -// import "allocator/tlsf"; -import {handler} from "./dice"; -import {JSONDecoder, JSONHandler} from "../node_modules/assemblyscript-json/assembly/decoder"; +import "allocator/tlsf"; //import "allocator/buddy"; //import "allocator/arena"; +import {handler} from "./game_handler"; + export function allocate(size: i32) :i32 { return memory.allocate(size); } @@ -20,12 +20,7 @@ export function invoke(ptr: i32, size: i32): i32 { bb[i] = load(ptr + i) } - let jsonHandler = new ToDictJSONEventsHandler(); - let decoder = new JSONDecoder(jsonHandler); - - decoder.deserialize(bb); - - let result = jsonHandler.strings.get("test"); + let result = handler(bb); let strLen: i32 = result.length; let addr = memory.allocate(strLen + 4); @@ -42,84 +37,3 @@ export function invoke(ptr: i32, size: i32): i32 { return addr; } - -class JoinRequest {} -class RollRequest { - public player_id: u64; - public bet_placement: u8; - public bet_size: u32; - constructor(player_id: u64 ,bet_placement: u8, bet_size: u32) { - this.player_id = player_id; - this.bet_placement = bet_placement; - this.bet_size = bet_size; - } -} -class GetBalanceRequest { - public player_id: u64; - constructor(player_id: u64) { - this.player_id = player_id; - } -} - -class JoinResponse { - player_id: u64; - constructor(player_id: u64) { - this.player_id = player_id; - } -} -class RollResponse { - public outcome: u8; - public player_balance: u64; - constructor(outcome: u64 ,player_balance: u8, bet_size: u32) { - this.outcome = outcome; - this.player_balance = player_balance; - } -} -class GetBalanceResponse { - public player_balance: u64; - constructor(player_balance: u64) { - this.player_balance = player_balance; - } -} -class ErrorResponse { - message: string; - constructor(message: string) { - this.message = message; - } -} - -class ToDictJSONEventsHandler extends JSONHandler { - - public strings: Map = new Map(); - public booleans: Map = new Map(); - public integers: Map = new Map(); - - setString(name: string, value: string): void { - this.strings.set(name, value); - } - - setBoolean(name: string, value: bool): void { - this.booleans.set(name, value); - } - - setNull(name: string): void { - } - - setInteger(name: string, value: i64): void { - this.integers.set(name, value); - } - - pushArray(name: string): bool { - return true; - } - - popArray(): void { - } - - pushObject(name: string): bool { - return true; - } - - popObject(): void { - } -} diff --git a/assembly/request.ts b/assembly/request.ts new file mode 100644 index 0000000..d44965f --- /dev/null +++ b/assembly/request.ts @@ -0,0 +1,81 @@ +import {JSONDecoder, JSONHandler} from "../node_modules/assemblyscript-json/assembly/decoder"; + +export abstract class Request {} + +export class UnknownRequest extends Request {} +export class JoinRequest extends Request {} +export class RollRequest extends Request { + public readonly playerId: u64; + public betPlacement: u8; + public betSize: u32; + constructor(playerId: u64, betPlacement: u8, betSize: u32) { + super(); + this.playerId = playerId; + this.betPlacement = betPlacement; + this.betSize = betSize; + } +} +export class GetBalanceRequest extends Request { + public playerId: u64; + constructor(playerId: u64) { + super(); + this.playerId = playerId; + } +} + +export function decode(bytes: Uint8Array): Request { + let jsonHandler = new RequestJSONEventsHandler(); + let decoder = new JSONDecoder(jsonHandler); + + decoder.deserialize(bytes); + + let action = jsonHandler.action; + + if (action === "Join") { + return new JoinRequest(); + } else if (action === "Roll") { + return new RollRequest(jsonHandler.playerId, jsonHandler.betPlacement, jsonHandler.betSize) + } else if (action === "GetBalance") { + return new GetBalanceRequest(jsonHandler.playerId) + } else { + return new UnknownRequest(); + } +} + +class RequestJSONEventsHandler extends JSONHandler { + + public action: string; + public playerId: u64; + public betPlacement: u8; + public betSize: u32; + public outcome: u8; + public playerBalance: u64; + + setString(name: string, value: string): void { + if (name === "action") { + this.action = value; + } + // json scheme is not strict, so we won't throw an error on excess fields + } + + setInteger(name: string, value: i64): void { + + if (name == "playerId") { + this.playerId = value as u64; + } else if (name === "betPlacement") { + this.betPlacement = value as u8; + } else if (name === "betSize") { + this.betSize = value as u32; + } else if (name === "outcome") { + this.outcome = value as u8; + } else if (name === "playerBalance") { + this.playerBalance = value as u64; + } + + // json scheme is not strict, so we won't throw an error on excess fields + } + + pushObject(name: string): bool { + return true; + } +} diff --git a/assembly/response.ts b/assembly/response.ts new file mode 100644 index 0000000..b5bc440 --- /dev/null +++ b/assembly/response.ts @@ -0,0 +1,73 @@ +import {JSONEncoder} from "../node_modules/assemblyscript-json/assembly/encoder"; + +export abstract class Response { + serialize(): string { + unreachable(); + return ""; + }; +} + +export class JoinResponse extends Response { + playerId: u64; + constructor(playerId: u64) { + super(); + this.playerId = playerId; + } + + serialize(): string { + let encoder = new JSONEncoder(); + encoder.setString("action", "Join"); + encoder.setInteger("playerId", this.playerId); + + return encoder.toString(); + } +} +export class RollResponse extends Response { + public outcome: u8; + public playerBalance: u64; + + constructor(outcome: u64, playerBalance: u8) { + super(); + this.outcome = outcome; + this.playerBalance = playerBalance; + } + + serialize(): string { + let encoder = new JSONEncoder(); + encoder.setString("action", "Roll"); + encoder.setInteger("outcome", this.outcome as i32); + encoder.setInteger("playerBalance", this.playerBalance as i32); + + return encoder.toString(); + } +} +export class GetBalanceResponse extends Response { + public playerBalance: u64; + constructor(playerBalance: u64) { + super(); + this.playerBalance = playerBalance; + } + + serialize(): string { + let encoder = new JSONEncoder(); + encoder.setString("action", "GetBalance"); + encoder.setInteger("playerBalance", this.playerBalance as i32); + + return encoder.toString(); + } +} +export class ErrorResponse extends Response { + message: string; + constructor(message: string) { + super(); + this.message = message; + } + + serialize(): string { + let encoder = new JSONEncoder(); + encoder.setString("action", "Error"); + encoder.setString("message", this.message); + + return encoder.toString(); + } +}