Death-Mask Wild Beta

Game Death-Mask goes into the status of a public beta (wild beta)
Reworked the main menu screen of the game, added a view of the blue zone of the techno-labyrinth, with pleasant music in the background.

Next, I plan to rework the gameplay controller, add smooth movement like in old shooters, high-quality 3D models of boxes, weapons, enemies, the ability to move to other levels of the techno-maze not only through portals (elevators, doors, fall through holes in the floor, holes in the walls), add a little variety to the environment of the generated maze. I will also work on the game balance.
Skeletal animation will be added at the polishing stage before release.
You can check public beta here:
http://demensdeum.com/games/deathMask/wildBeta/

Prototype Pattern

The pattern “Prototype” refers to the group of creational patterns.
Suppose we are developing applications for dating Tender, according to the business model, we have a paid opportunity to make copies of our own profile by changing the name automatically, and the order of photos in places. This is done in order for the user to be able to conduct several profiles at once with a different set of friends in the application.
By clicking on the button to create a copy of the profile, we need to implement a profile copy, auto name generation and re-sort the photos.
Pseudocode:


fun didPressOnCopyProfileButton() {
    let profileCopy = new Profile()
    profileCopy.name = generateRandomName()
    profileCopy.age = profile.age
    profileCopy.photos = profile.photos.randomize()
    storage.save(profileCopy)
}

Now imagine that other team members made a copy-paste copy code or invented it from scratch, and after that a new field was added – likes. This field stores the number of profile likes, now you need to update * all * of the place where copying takes place manually, adding a new field. It is very long and difficult to maintain the code, as in testing.
To solve this problem, a prototype design pattern was invented. Let’s create a common copying protocol, with the copy () method which returns a copy of the object with the required fields. After changing the entity fields, you will need to update only one copy() method, instead of manually searching and updating all places containing the copy code.

References

https://refactoring.guru/design-patterns/prototype

State machine and State pattern

In this article I will describe the use of the state machine, I will show a simple implementation, an implementation using the State pattern. It is worth mentioning that the use of the State pattern is undesirable in the presence of fewer than three states, since this usually leads to an excessive complication of the readability of the code, accompanying support problems – everything should be in moderation.

MEAACT PHOTO / STUART PRICE.

Flags Ruler

Suppose we are developing a video player screen for a civil aircraft media system, the player must be able to download the video stream, play it, allow the user to stop the download process, rewind and perform other ordinary operations for the player.
Suppose the player has cached the next chunk of the video stream, checked that there were enough chunks to play, began to play the fragment to the user and at the same time continues to download the next one.
At this point, the user rewinds to the middle of the video, so now you need to stop playing the current track, and start loading from a new position. However, there are situations in which this can not be done – the user can not control the playback of the video stream, while he is shown a video about safety in the air. Get the isSafetyVideoPlaying flag to check this situation.
Also, the system should be able to pause the current video and broadcast an alert from the captain of the vessel and crew through the player. Let’s get another flag isAnnouncementPlaying. Plus, there was a requirement not to pause the playback while displaying help on working with the player, one more flag isHelpPresenting.

Pseudocode:


class MediaPlayer {

    public var isHelpPresenting = false
    public var isCaching = false
    public var isMediaPlaying: Bool = false
    public var isAnnouncementPlaying = false
    public var isSafetyVideoPlaying = false

    public var currentMedia: Media = null

    fun play(media: Media) {

        if isMediaPlaying == false, isAnnouncementPlaying == false, isSafetyVideoPlaying == false {

            if isCaching == false {
                if isHelpPresenting == false {
                    media.playAfterHelpClosed()
                }
                else {
                    media.playAfterCaching()
                }
            }
    }

    fun pause() {
        if isAnnouncementPlaying == false, isSafetyVideoPlaying == false {
            currentMedia.pause()
        }
    }
}

The above example is poorly readable, such code is difficult to maintain due to the large variability (entropy) This example is based on my experience with the code base * of many * projects where the state machine was not used.
Each flag should “manage” the interface elements in a special way; the application’s business logic, the developer, adding the next flag, should be able to juggle them, checking and re-checking everything several times with all possible options.
Substituting into the formula “2 ^ number of flags” you can get 2 ^ 6 = 64 variants of application behavior for a total of 6 flags, all these combinations of flags will need to be checked and maintained manually.
From the developer’s side, adding a new functionality with this system looks like this:
– You need to add the ability to show the browser page of the airline, while it should be minimized as with movies, if the crew members announce something.
– Ok, I will. (Oh, damn, you’ll have to add one more flag, and double-check all the places where the flags intersect, that’s how much you need to change!)

Also a weak point of the flag system is making changes to the behavior of the application. It is very difficult to imagine how to quickly / flexibly change the behavior based on the checkboxes, if, after changing only one flag, you have to double-check everything. This approach to development leads to a lot of problems, loss of time and money.

Enter The Machine

If you take a good look at the flags, you can understand that in fact we are trying to handle specific processes taking place in the real world. We list them: the usual mode, the display of video security, broadcast messages from the captain or crew. For each process, a set of rules is known that change the behavior of the application.
According to the rules of the state machine, we will list all the processes as states in enum, add such a concept as state to the player code, implement the behavior based on the state, removing the combinations on the checkboxes. Thus, we reduce the options for testing to exactly the number of states.

Pseudocode:


enum MediaPlayerState {
	mediaPlaying,
	mediaCaching,
	crewSpeaking,
	safetyVideoPlaying,
	presentingHelp
}

class MediaPlayer {
	fun play(media: Media) {
		media.play()
	}

	func pause() {
		media.pause()
	}
}

class MediaPlayerStateMachine {
	public state: MediaPlayerState
	public mediaPlayer: MediaPlayer
	public currentMedia: Media

	//.. init (mediaPlayer) etc

	public fun set(state: MediaPlayerState) {
		switch state {
			case mediaPlaying:
				mediaPlayer.play(currentMedia)
			case mediaCaching, crewSpeaking,
			safetyVideoPlaying, presentingHelp:
				mediaPlayer.pause()
		}
	}
}

The huge difference between the flag system and the state machine lies in the logic state switching funnel in the set (state: ..) method, it allows you to translate the human understanding of the state into the program code, without having to play logic games to convert the flags to states with further support of the code .

State pattern

Next, I will show the difference between the naive implementation of the state machine and the state pattern. Imagine that you need to add 10 steits, as a result, the state machine class will grow to the size of a godobject, it will be difficult and expensive to maintain. Of course, this implementation is better than the flag, (if the flag system is shot first by the developer, and if not, after seeing 2 ^ 10 = 1024 variations, QA hangs, but if both of them * do not notice the complexity of the task, then the user who has the application just refuses to work with a certain combination of flags)
With a large number of states, it is necessary to use the State pattern.
We will issue a set of rules in the protocol of the State:


protocol State {
    func playMedia(media: Media, context: MediaPlayerContext)
    func shouldCacheMedia(context: MediaPlayerContext)
    func crewSpeaking(context: MediaPlayerContext)
    func safetyVideoPlaying(context:MediaPlayerContext)
    func presentHelp(context: MediaPlayerContext)
}

We will carry out the implementation of the set of rules in separate states, for example, the code of one state:


class CrewSpeakingState: State {
	func playMedia(context: MediaPlayerContext) {
		showWarning(“Can’ t play media - listen to announce!”)
	}

	func mediaCaching(context: MediaPlayerContext) {
		showActivityIndicator()
	}

	func crewSpeaking(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func safetyVideoPlaying(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func presentHelp(context: MediaPlayerContext) {
		showWarning(“Can’ t present help - listen to announce!”)
	}
}

Next, create a context with which each state will work, integrate the state machine:


final class MediaPlayerContext {
	private
	var state: State

	public fun set(state: State) {
		self.state = state
	}

	public fun play(media: Media) {
		state.play(media: media, context: this)
	}

	…
	Other events
}

Application components work with the context through public methods, the state objects themselves decide from which state to which to make the transition using the state machine inside the context.
Thus, we have implemented the God Object decomposition, and it will be much easier to maintain a changing state by tracking the changes in the protocol by the compiler, reducing the complexity of understanding the states due to reducing the number of lines of code and focusing on solving a specific state task. Also, you can now share teamwork, giving the implementation of a particular state to team members, without worrying about the need to “resolve” conflicts, which happens when working with one large class of state machines.

References

https://refactoring.guru/design-patterns/state

Skeletal Animation (part 1 – shader)

In this article, I will describe my understanding of skeletal animation, which is used in all modern 3D engines to animate characters, game environments, etc.
I will start the description from the most simple part – the vertex shader, because the whole way of the calculations, no matter how complicated it was, ends with the transfer of the prepared data to render in the vertex shader.

Skeletal animation after calculating the CPU enters the vertex shader.
I recall the vertex formula without skeletal animation:
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vertex;
For those who do not understand how this formula originated, you can read my article describing the principle of working with matrices to display 3D content in the context of OpenGL.
For the rest – the formula for the implementation of skeletal animation:
” vec4 animatedVertex = bone0matrix * vertex * bone0weight +”
“bone1matrix * vertex * bone1weight +”
“bone2matrix * vertex * bone2weight +”
“bone3matrix * vertex * bone3weight;\n”
” gl_Position = projectionMatrix * viewMatrix * modelMatrix * animatedVertex;\n”

Ie the final matrix of bone transformation is multiplied by the vertex and the weight of this matrix relative to the vertex. Each vertex can be animated with 4 bones, the force of the impact is regulated by the bone weight parameter, the sum of the effects should be equal to one.
What to do if the vertex is affected by less than 4 bones? It is necessary to divide the weight between them, the effect of the rest to be equal to zero.
Mathematically multiplying a weight by a matrix is ​​called “Multiplying a matrix by a scalar”. Multiplication by a scalar allows you to sum up the effects of the matrices on the final vertex.

The bone transformation matrixes themselves are passed in an array. Moreover, the array contains the matrix for the entire model as a whole, and not for each mesh separately.

But for each vertex the following information is transmitted separately:
– Matrix index that affects vertex
– The weight of the matrix that affects the vertex
More than one bone is transmitted, usually 4 bones per vertex are used.
Also the sum of the weights of the 4 bones should always be equal to one.
Next, consider how it looks in the shader.
Array of matrices:
“uniform mat4 bonesMatrices[kMaxBones];”

Information on the effect of 4 bones on each vertex:
“attribute vec2 bone0info;”
“attribute vec2 bone1info;”
“attribute vec2 bone2info;”
“attribute vec2 bone3info;”

vec2 – in the X coordinate, store the bone index (and translate to int in the shader), in the Y coordinate, the weight of the impact of the bone on the vertex. Why do you have to transfer this data in a two-dimensional vector? Because GLSL does not support the transfer of readable C structures with correct fields to the shader.

Below is an example of obtaining the necessary information from a vector, for further substitution in the formula animatedVertex:

“int bone0Index = int(bone0info.x);”
“float bone0weight = bone0info.y;”
“mat4 bone0matrix = bonesMatrices[bone0Index];”

“int bone1Index = int(bone1info.x);”
“float bone1weight = bone1info.y;”
“mat4 bone1matrix = bonesMatrices[bone1Index];”

“int bone2Index = int(bone2info.x);”
“float bone2weight = bone2info.y;”
“mat4 bone2matrix = bonesMatrices[bone2Index];”

“int bone3Index = int(bone3info.x);”
“float bone3weight = bone3info.y;”
“mat4 bone3matrix = bonesMatrices[bone3Index];”

Now the vertex structure that fills up on the CPU should look like this:
x, y, z, u, v, bone0index, bone0weight, bone1index, bone1weight, bone2index, bone2weight, bone3index, bone3weight

The structure of the vertex buffer is filled once at the time of loading the model, but the transformation matrices are transferred from the CPU to the shader at each rendering frame.

In the remaining parts, I will describe the principle of calculating animation on the CPU, before passing to the vertex shader, I will describe the tree of bones, the passage through the hierarchy of animation-model-node-mesh, matrix interpolation.

References

http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html

Source Code

https://gitlab.com/demensdeum/skeletal-animation

Template Method Pattern

The template method refers to behavioral design patterns. The pattern describes how to replace parts of class logic on demand, leaving the common part unchanged for posterity.

Cuban Cars

Suppose we are developing a bank client, consider the task of developing an authorization module – the user should be able to log in to the application using abstract login data.
The authorization module must be cross-platform, support different technologies of authorization and storage of encrypted data of different platforms. To implement the module, we choose the Kotlin cross-platform language using the abstract class (protocol) of the authorization module, we will write the implementation for the MyPhone phone:


class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    abstract fun authorize(authorizationData: AuthorizationData)
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
    override fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

Now for each phone / platform we will have to duplicate the code to send authorization to the server, there is a violation of the principle of DRY. The above example is very simple, there will be even more duplication in more complex classes. To eliminate code duplication, use the Template method pattern.
We will carry out the common parts of the module in immutable methods, we transfer the functionality of the transfer of encrypted data to specific classes of platforms:


class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    
    fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

References

https://refactoring.guru/design-patterns/template-method

Source code

https://gitlab.com/demensdeum/patterns/

Bridge Pattern

The Bridge pattern refers to the structural design patterns. It allows you to abstract the implementation of class logic using the transfer of logic into a separate abstract class. Sounds easy, right?

Suppose we implement a spam bot that should be able to send messages to different types of instant messengers.
We implement using a common protocol:


protocol User {
    let token: String
    let username: String
}

protocol Messenger {
    var authorize(login: String, password: String)
    var send(message: String, to user: User)
}

class iSeekUUser: User {
    let token: String
    let username: String
}

class iSeekU: Messenger {

    var authorizedUser: User?
    var requestSender: RequestSender?
    var requestFactory: RequestFactory?

    func authorize(login: String, password: String) {
        authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
    }
    
    func send(message: String, to user: User) {
        requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
    }
}

class SpamBot {
    func start(usersList: [User]) {
        let iSeekUMessenger = iSeekU()
        iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
        
        for user in usersList {
            iSeekUMessennger.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
        }
    }
}

Now let’s imagine the situation of the release of a new, faster message sending protocol for the iSekU messenger. To add a new protocol, it will be necessary to duplicate the implementation of the iSekU bot, changing only a small part of it. It is not clear why to do this if only a small part of the class logic has changed. With this approach, the principle of DRY is violated; with the further development of the product, the lack of flexibility will make itself known by mistakes and delays in the implementation of new opportunities.
We’ll take out the logic of the protocol in an abstract class, thus implementing the Bridge pattern:


protocol User {
    let token: String
    let username: String
}

protocol Messenger {
    var authorize(login: String, password: String)
    var send(message: String, to user: User)
}

protocol MessagesSender {
    func send(message: String, to user: User)
}

class iSeekUUser: User {
    let token: String
    let username: String
}

class iSeekUFastMessengerSender: MessagesSender {
    func send(message: String, to user: User) {
        requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
    }
}

class iSeekU: Messenger {

    var authorizedUser: User?
    var requestSender: RequestSender?
    var requestFactory: RequestFactory?
    var messagesSender: MessengerMessagesSender?

    func authorize(login: String, password: String) {
        authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
    }
    
    func send(message: String, to user: User) {
        messagesSender?.send(message: message, to: user)
    }
}

class SpamBot {

    var messagesSender: MessagesSender?

    func start(usersList: [User]) {
        let iSeekUMessenger = iSeekU()
        iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
        
        for user in usersList {
            messagesSender.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
        }
    }
}

One of the advantages of this approach is undoubtedly the ability to extend the functionality of the application, by writing plug-ins / libraries that implement the abstracted logic, without changing the code of the main application.
What is the difference with the Strategy pattern? Both patterns are very similar, but the Strategy describes the switching * of algorithms *, while the Bridge allows switching large parts * of any complex logic *.

References

https://refactoring.guru/design-patterns/bridge

Source Code

https://gitlab.com/demensdeum/patterns/

Chain of Responsibility Pattern

The chain of responsibility refers to the behavioral design patterns.


Ганна Долбієва

The Jah-pictures film company shot a documentary about the Rastaman communists from Liberia called “The Red Dawn of Marley”. The film is very long (8 hours), interesting, but before being presented worldwide it turned out that in some countries moments and phrases from films may be considered heresy and do not give a rolling license. Producers of the film decide to cut the moments containing risky phrases from the film, manually and by computer. A double check is needed to ensure that representatives of the distributor are not simply shot in some countries, in the event of an error during manual inspection and installation.
Countries are divided into four groups – countries without censorship, with moderate, medium and very strict censorship. The decision is made to use neural networks to classify the level of heresy in the viewing fragment of the film. For the project, very expensive state-of-art neurons purchased with different levels of censorship detection are purchased, the developer’s task is to break the film into fragments and transfer them to chain of neural networks, from moderate to strict, until one of them detects heresy, then the fragment is sent to manual cut. It is impossible to make a walk through all the NNs, because too much computing power is spent on their work (we still have to pay for the electricity), it should stop on the first classify.
Naive pseudocode implementation:


import StateOfArtCensorshipHLNNClassifiers

protocol MovieCensorshipClassifier {
    func shouldBeCensored(movieChunk: MovieChunk) -> Bool
}

class CensorshipClassifier: MovieCensorshipClassifier {

    let hnnclassifier: StateOfArtCensorshipHLNNClassifier

    init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier) {
        self.hnnclassifier = hnnclassifier
    }
    
    func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
        return hnnclassifier.shouldBeCensored(movieChunk)
    }
}

let lightCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"))
let normalCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal"))
let hardCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard"))

let classifiers = [lightCensorshipClassifier, normalCensorshipClassifier, hardCensorshipClassifier]

let movie = Movie("Red Jah rising")
for chunk in movie.chunks {
    for classifier in classifiers {
        if classifier.shouldBeCensored(chunk) == true {
            print("Should censor movie chunk: \(chunk), reported by \(classifier)")
        }
   }
}

In general, the whole solution with an array of classifiers is not so bad, however! let’s imagine that we cannot create an array, we have the opportunity to create only one entity of the classifier, which already determines the type of censorship for a fragment of a film. Such restrictions are possible in the development of the library extending the functionality of the application (plugin).
We will use the decorator pattern – we will add the reference class to the next classifier in the classifier, we will stop the verification process at the first successful classification.
Thus, we implement the Chain of Responsibility pattern:


import StateOfArtCensorshipHLNNClassifiers

protocol MovieCensorshipClassifier {
    func shouldBeCensored(movieChunk: MovieChunk) -> Bool
}

class CensorshipClassifier: MovieCensorshipClassifier {

    let nextClassifier: CensorshipClassifier?
    let hnnclassifier: StateOfArtCensorshipHLNNClassifier

    init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier, nextClassifier: CensorshipClassifiers?) {
            self.nextClassifier = nextClassifier
            self.hnnclassifier = hnnclassifier
    }
    
    func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
        let result = hnnclassifier.shouldBeCensored(movieChunk)
        
        print("Should censor movie chunk: \(movieChunk), reported by \(self)")
        
        if result == true {
                return true
        }
        else {
                return nextClassifier?.shouldBeCensored(movieChunk) ?? false
        }
    }
}

let censorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"), nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal", nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard")))))

let movie = Movie("Red Jah rising")
for chunk in movie.chunks {
    censorshipClassifier.shouldBeCensored(chunk)
}

References

https://refactoring.guru/design-patterns/chain-of-responsibility

Source Code

https://gitlab.com/demensdeum/patterns/