Skip to content

Implementation Document

Yiou Chen edited this page Jan 4, 2019 · 6 revisions

This document is outdated

Technologies:

General:

  • prettier JavaScript code formatter
  • Jest JavaScript unit testing

Server:

  • Socket.io websocket implementation in nodeJS

Client

Not decided.

Data structures

Game board

  • an array of size n x m
  • Each element in the array represent a tile
  • Each road has reference to the next roads, each property has reference to the connected properties. Each road tile has reference to the property tie to this road. (roads cannot be properties)
  • Tiles can be accessed using the index or coordinates, each tile also stores its own index and coordinates
  • Starting position
  • Properties has base price

Game state:

  • Current player
  • Game state (started, finished)
  • Player has location in the board (x, y)
  • Player has direction that he is traveling
  • Player has last path travelled, denoted in an array of coordinates [(x1, y1), (x2, y2), (x3, y3)]
  • Player has cash value
  • Player has index, player can be accessed using index, player also stores its own index
  • Property has rent, initial rent is 0
  • Property has ownership, denoted by player’s index

Messages:

Messages:

Client and server communicate through messages. We follow the same format for messages. Each message contains two parts: type and data

General format

type: String
data: Object

For example:

socket.emit('ACTION_TYPE',  { response: 'yes'});

Client messages for rooms

Client login

type: LOGIN
data: {username: 'zebra'}
// server will send back an clientId

Client reconnect

type: RECONNECT
data: {userId: '123'}

Client request join room

type: JOIN
data: {
	roomId: ‘unique id’
}

Client leaving room

type: LEAVE
data: {}

Client ready

type: READY
data: {}

Host start

type: START
data: {}

Host kick another client

type: KICK
data: { userId: '12345' }
// userId is another user's id

Client create room

type: CREATE_ROOM
data: {roomName: 'desert'}
// server send back room id

Client chat

type: CHAT
data: {message: 'Hello'}
// publicly visible in the current room

Server messages for rooms

Server sends message back to client upon client request

Client login

type: LOGIN_ACCEPT
data: {}

Client request join room

type: JOIN_ACCEPT
data: {}

type: JOIN_ERROR
data: {
    errorType: 'ROOM_NOT_FOUND' or ...
}

Send room information to client

// server will send this immediately to client when they join.
// Also when there is any update
type: ROOM_STATE
data: {...}

Client leaving room

type: LEAVE_ACCEPT
data: {}

Client ready

type: READY_ACCEPT
data: {}

Host start

// send to all client
type: GAME_START
data: {}

Client disconnect

// To be decided

Host kick another client

type: KICK_ACCEPT
data: {}

type KICK_ERROR
data: {
    errorType: 'USER_NOT_FOUND', 'NO_PERMISSION' ...
}

Client create room

type: CREATE_ROOM_ACCEPT
data: {roomId: '12345'}

type: CREAT_ROOM_ERROR
data: { errorType: 'TOO_MANY_ROOMS'}

Client chat

type: CHAT
data: {message: 'Hello', messageId: 1, username: "name", extra: {} }
// id increases when we have new message.
// publicly visible in the current room

Client messages for game

Client roll dice

type: DICE
data: {}

Passive response

type: RESPONSE
data: { response: 'yes', actionId: 1 }

Server messages for game

Game state update

type: GAME_STATE
data: {...}

Game state

The state of the game is composed of two parts. The static part and the dynamic part.

The static game states can be initialized at the beginning of the game. We don't need to recalculate them throughout the game. Some static states include the connectivity among tiles, neighborhoods.

The dynamic game states are updated throughout the game and whenever they are updated, server will push new game states to client. Server will always push the current game state to clients, instead of just the diff for the last update. This is to make sure that even if a client disconnect for a while, it can still receive the whole game states that it needs. Some dynamic states include players' cash value, properties' rents and ownership.

Server stores all the game state. When it needs to update a client, it extracts out a portion that a client needs. For example, if we have a client that only displays the current leaderboard based on player's cash, we can extract out only the cash value for each player and ignore other game states.

Dynamic game state

{
    currentPlayerLastMove: [[x,y], [x, y], [x,y]],
    currentPlayer: id
    players: [
        {
            cash: 1000,
            position: [x, y],
            id: 1,
            direction: 'UP', 'DOWN', 'LEFT', 'RIGHT',
            status: 'LOST', 'JAIL'...
            currentActionID: [1, 2]
        }
    ],
    properties: {
        name{
            id1: {
                rent: 1000,
                position: [x, y],
                onwer: undefined or playerId
            },
            id2: {

            }
        }
        
    }
    
}

Static game state

Board state

// TODO

{
    // TODO
    [0,1] {
        baseRent: 122
        id: id123
        neighborhood: name
    }
}

currentActionArray = [buyHouseActionHandlers, buyStockActionHander]

var playerActions = [] playerActions[0].currentAction



client: socket.emit("yes", {answer: "yes"});

serer: gameState.client1.currentAction = buyStockActionHandler; socket.emit('do you want to buy');

var buyHouseActionHandlers ={
    onYes: function() {
    
    },
    onNo: function() {
    
    }
}
var buyStockActionHander() {
    onYes: function() {
    
    }
}

socket.on("yes", function(){
    gameState.client1.currentAction.onYest();
});

# Data Validation
Server side can perform data validation through `server/objects`. All the types in state will 
have a coresponding schema. A schema looks like below:

const roomSchema = { id: Types.isString, name: Types.isString, users: Types.isObjectOf(isUserState), };

A room object that matches this schema could be:

const room = { id: 'room12', name: 'nice room', users: {} };


The basic types are defined in `server/objects/baseTypes`.
To create a complex type(such as Room), we need to create a function that takes in value, and path, and return
an array of validation errors. All the current types return an array of errors, so we can build on top of them.

`value` is the value that we are validating, path is the relative path that leads to this value. For example:
If we need to validate the room inside following state:

{ global: { rooms: [{ id: 'room12', name: 'nice room', users: {} }] } }

The `value` is 

{ id: 'room12', name: 'nice room', users: {} }

and the `path` is `".global.rooms[0]"`.

If there is no error, the validation function should return an empty array.

# Hosting: 
## Naming convention
* File/Folder name: camelCase
* Function name: camelCase
* Variable name: camelCase
* Class name: CamelCase
* Constants: SNAKE_CASE

# Random things: 
**Update a rent:** find the property tie to the road, traverse to left, and to right of the linked list, update the rent on all connected properties owned by the same owner before th update.