dominicprice / endplay

A suite of tools for generation and analysis of bridge deals. Read the documentation at https://endplay.readthedocs.io
MIT License
21 stars 5 forks source link

Json serialization of Boards #14

Closed Anakin100100 closed 2 years ago

Anakin100100 commented 2 years ago

This library offers unparalleld support for parsing to and from .lin .pbn and for double dummy analysis and for hand generation and analysis. But it is written in Python, which is not a bad thing. This library has to be written in some programming language and Python really fits it. Unfortunelety most web services are not written in Python or in any other programming language. Adding support for easy serialization to and from json would open this amazing library to projects in all programming languages.

Right now I'm writing an app for managing collection of deals for bridge coaches and being able to access what this library offers using a minimalistic wrapper in Ruby on Rails would be amazing. In particular the exports to and from lin and pbn would be huge life savers for me. Unfortunetely instead of writing the pbn and lin export and import code I would have to write serialization to and from some type of json that I could send to fastapi or cloud functions based microservice. and be able to process that in the database at the same time for performing modifications and CRUD operations. This is much easier than reinventing the wheen and writing the pbn and lin exports myself but it still would be error prone and ineffecient.

@dominicprice you did an awesome job with this library and I think that being able to easily use it's powers in other languages would really enhance it. I would be really grateful to you if you could extend the library in such a way if it wouldn't be too burdensome for you.

dominicprice commented 2 years ago

So I actually thought about this a bit and even starting brainstorming some to_json methods but the problem is there is no standard format for 'bridge JSON'. There was at some point talk of forming a standard on the bridge-dev groups.io, and in particular the 'Bridge Standards Working Group' was going to work on this but there doesn't seem to be much progress.

RE integration with other languages, and in particular web development, I have in the past played around with Transcrypt which is a cross-compiler from Python to JavaScript. Combining this with WASM (for porting the original DDS library which is written in C, work on this has been done e.g. here and I have also worked on my own fork for this) would allow full client-side use of the endplay library with hopefully very little work as both Transcrypt and endplay are compatible with Python 3.7. For server-side languages, this also means compatibility with node.js.

All that being said, I think that you are right that the most sensible first step is to allow importing and exporting from a common serialization format, and although I don't claim to have the perfect abstraction for what a "Board" is, a JSON object representation of the Board class in endplay is probably a fair compromise for the moment and is fairly easy to implement so I'm happy to add in the code, however there are a couple of design choices which need to be clarified:

  1. Should a Card be represented as e.g. "S9" or { "suit": "spades", "rank": "9" }?
  2. What is the best way to encode a bid? It should obviously(?) be an object with "alertable": true/false and "announcement: "blah blah" properties, but then should it just have a "name": "3NT"/"name": "double" property or "level": 3, "strain": "nt"/"penalty": "double" depending on the type of the bid?

Feedback appreciated :)

Anakin100100 commented 2 years ago

When it comes to how to represent a card I would lean more towards { "suit": "spades", "rank": "9" } implementation because it should play nicer with validations and filtering on the server. For the same reasons the more verbose implementation of a bid would be a bit nicer and more extensible for custom needs.

dominicprice commented 2 years ago

I've pushed an prototype implementation of JSON encoding/decoding to master in 31ae9d900468ca3f48ad6fe89439b12d38e681a0. It's basically the Board class verbatim transformed to a JSON object as described above, with the usual load/loads and dump/dumps interface --- in fact it the endplay.parsers.json module is simply an extension of the builtin json library but with specialisations for the endplay classes. The main difficulty is due to the fact that Player, Vul, Denom, Rank and Penalty are implemented as IntEnum classes which the json module attempts to serialise as plain ints before the custom mechanisms from the derived classes get a chance to perform custom work; there is a question about this on stack overflow but it doesn't really help as the top-level Board object is the only one which iterencode gets called on, so all the endplay types are special-cased to ensure that any of the IntEnums they contain get serialised correctly.

tl;dr: json support added but it probably needs a bit of debugging.

This did get me thinking though --- perhaps a better long-term approach for serialisation is to implement a RPC protocol, without any actual rpc services but just making use of the structured message types and cross-language code generation support to allow the Python objects to be implemented in different languages. At my work we use gRPC a lot (mainly for its very good support for Go), but I have also come across Apache Thift in the past which seems to be a good project for implementing cross-language services. I'll give this some more thought, but if you have any input I would be interested to hear your thoughts :).

Anakin100100 commented 2 years ago

It looks really well. I thought you were going to need at least a week to finish a prototype. Do you think that implementing the vulnerable sites as vulnerable_ns: true, vulnerable_ew: false instead of the current one would be better? I think it would allow for easier updates and validations.

dominicprice commented 2 years ago

Hi, sorry for the delay getting back; busy week at work... I'm not against the idea of representing vulnerability like that but I'm not totally sure I understand the situations in which it leads to clearer code, especially when it comes to serialising/deserialising the JSON object. How do you currently encode the vulnerability in your Ruby code? I'm not a Ruby programmer, but I'd assume that for maximum compatibility with the DDS routines the most convenient way would be with some named constants e.g.

module Vul
  NONE = 0
  BOTH = 1
  NS = 2
  EW = 3
end

rather than a class which holds the vulnerability of each side

class Vul
  def initialize(ns, ew)
    @ns = ns
    @ew = ew
  end
end
Anakin100100 commented 2 years ago

I currently encode them as separate boolean columns. This leads to cleaner code but in areas other than json serialization. When it comes to updating a board I can blindly copy the values from a form but with this I have to consider four cases with separate if statements or match cases. The same thing happens in views where I have two simple if statements one for the north south pair and one for the east west pair. With named constants three cases have to be considered. Having them as separate columns would also allow for easier filtering and would make it easier to understand the code in general. I think that the vulnerability is additive in nature and separate for both sides so it should be separated. Although named constants should be a better fit when it comes to json serialization and deserialization not to mentions the dds solver that was written in C++.

dominicprice commented 2 years ago

Yeah I think that sounds like a useful way of representing them in internal structures but it is less intuitive for a serialisation format. I think somewhere in the endplay source code is a function along the lines of

def is_vul(player, vul):
  if vul == Vul.Both: return true
  if vul == Vul.ns and player in [Player.north, Player.south]: return true
  if vul == Vul.ew and player in [Player.east, Player.west]: return true
  return false

although its probably common enough that I should make it a method of the Vul class.

I've drafted up schemas for the JSON representation which are hosted here and which can be used to validate a JSON object or as a way to view the expected structure; I might run it by the bridge groups.io and make it a bit easier to navigate as I just ran it through an autodoc generator and the output is a bit clunky.

dominicprice commented 2 years ago

The json module is now documented in the readme and has been released for a while so I am closing this issue, any bugs should be raised as a new issue.