Skip to content

Latest commit

 

History

88 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gwent

This is a project for a design and programming patterns subject in university. The idea is to try to program a card game based on the video game "The witcher 3".

Design choices

Most of the code was written in a way were it wouldn't have to be written more than one time the best example is the player: When the player has to draw a card or play a card most of the work is done in the Deck and Hand classes.

Cards where created with future proofing. There is an abstract class with the common things that all cards need in a way that when gameplay has to be implemented the only thing thats going be changed is the methods inside the cards/deck/hand and that there won't be need of creating new classes.

Also most of the code is separated in many packages and scala files/classes/traits so it's easier to work with some specific function and it helps with order.

For the second assignment i created a UML diagram that can be found in the UML_Diagram.md file.

Related to the second assignment i tried to do most of the code in double dispatch so the code is more adaptable to future changes. The board is one that is divided in four zones and each one has a zone to each specific card. When a special ability is used the cards have their original value saved for when the effect is disipated.

Mostly that and that special Effects for climate cards are their classes because they already existed.

So now the project is divided in two packages "model" that is where most of the work made in the first and second assignment was done(and some more in this assignment so the design works because it had a mayor error related to cards not adding correctly to zones), and controller that is the work made in this third and final assignment, so basicly a MVC without the View.

The controller works in the following way, it first creates all the things needed to "play" the game. Then it waits for player input, and then the game follows depending on the players actions, computer ai, etc. Mostly i tried to make it really clear what every state does, computerTurn is the computers turn and contains most of what the computer does except play a random card, that is in PlayRandom, Start starts a Round, etc. They extend from a AbstracClass so I don't have to write change state in every class. The observer is there mostly so it sees the changes in states and notifies the player and itself

Code adaptability

The code was designed in a way that if some functionality has to be added to a class it would be quick and not create problems.

Design patterns

In this project i used the following design patterns: Observer(To see the changes made with controller), Visitor(In Card,AbstractUnit,AbstractClimate,Melee,etc. So the tests and the intended method of effects and climate cards worked succesfully), Model-View-Controller(Its in the package names) and State(There is a package named State).

Tests

At the moment the tests are concerned with the classes working correctly. Most of the gameplay has been not implemented yet so interactions relating to the "game" haven't been tested completely. There are tests so the player can draw cards and that it's hand/deck changes correctly.

Also i put the names of characters from my favourite videogame(s) in the tests. Nothing related to how the code works but i figured it's interesting to tell. And i love that game.

There are two classes that are untestable :( ConsoleSubject and Play, because both expect a player input I ask if that could be considered when reviewing the project.

English

Most of the repository, documentation and the README.md is written in english to be more professional. The only parts not written in english are the ClimateCard names because i don't know their name in english.

UML Diagram

classDiagram
    %% ==================== CARD PACKAGE ====================
    
    class Card {
        <<trait>>
        +getType() String
        +getValue() Int
        +playByComputer(player: Computer, board: Board) Unit
        +tryPlayWeather(player: Computer, board: Board) Boolean
        +playByPlayer(player: Human, board: Board) Unit
    }
    
    %% Unit Cards
    class UnitCard {
        <<trait>>
        +getName() String
        +getType() String
        +getValue() Int
        +getRange() String
        +getSpecialAbility() Effect
        +increaseValue(amount: Int) Unit
        +decreaseValue(amount: Int) Unit
        +setValue(amount: Int) Unit
    }
    
    class AbstractUnit {
        <<abstract>>
        -name: String
        -cardType: String
        -value: Int
        -range: String
        -specialAbility: Effect
        -originalValue: Int
        +getName() String
        +getType() String
        +getValue() Int
        +getRange() String
        +getSpecialAbility() Effect
        +increaseValue(amount: Int) Unit
        +decreaseValue(amount: Int) Unit
        +setValue(amount: Int) Unit
        +resetValue() Unit
        +tryPlayWeather(computer: Computer, board: Board) Boolean
        +playByComputer(player: Computer, board: Board) Unit
        +playByPlayer(player: Human, board: Board) Unit
        +equals(other: Any) Boolean
    }
    
    class Melee {
        +name: String
        +value: Int
        +specialAbility: Effect
        +isMelee() Boolean
        +playByComputer(player: Computer, board: Board) Unit
        +playByPlayer(player: Human, board: Board) Unit
    }
    
    class Ranged {
        +name: String
        +value: Int
        +specialAbility: Effect
        +isRanged() Boolean
        +playByComputer(player: Computer, board: Board) Unit
        +playByPlayer(player: Human, board: Board) Unit
    }
    
    class Siege {
        +name: String
        +value: Int
        +specialAbility: Effect
        +isSiege() Boolean
        +playByComputer(player: Computer, board: Board) Unit
        +playByPlayer(player: Human, board: Board) Unit
    }
    
    %% Climate Cards
    class ClimateCard {
        <<trait>>
        +getClimate() String
        +applyClimateEffect(board: Board) Unit
        +unapplyClimateEffect(board: Board) Unit
    }
    
    class AbstractClimate {
        <<abstract>>
        -cardType: String
        -climate: String
        +getClimate() String
        +getType() String
        +getValue() Int
        +applyClimateEffect(board: Board) Unit
        +unapplyClimateEffect(board: Board) Unit
        +playByComputer(player: Computer, board: Board) Unit
        +playByPlayer(player: Human, board: Board) Unit
        +tryPlayWeather(computer: Computer, board: Board) Boolean
        +equals(other: Any) Boolean
    }
    
    class ClimaDespejado {
    }
    
    class EscarchaMordiente {
        +applyClimateEffect(board: Board) Unit
        +unapplyClimateEffect(board: Board) Unit
    }
    
    class LluviaTorrencial {
        +applyClimateEffect(board: Board) Unit
        +unapplyClimateEffect(board: Board) Unit
    }
    
    class NieblaImpenetrable {
        +applyClimateEffect(board: Board) Unit
        +unapplyClimateEffect(board: Board) Unit
    }
    
    %% Effects
    class Effect {
        <<trait>>
        +applyPlayerEffect(board: Board, name: String) Boolean
        +applyComputerEffect(board: Board, name: String) Boolean
        +getEffect() String
    }
    
    class AbstractEffect {
        <<abstract>>
        -effect: String
        +applyPlayerEffect(board: Board, name: String) Boolean
        +applyComputerEffect(board: Board, name: String) Boolean
        +getEffect() String
        +equals(other: Any) Boolean
    }
    
    class NoEffect {
    }
    
    class VinculoEstrecho {
        +applyPlayerEffect(board: Board, name: String) Boolean
        +applyComputerEffect(board: Board, name: String) Boolean
    }
    
    class RefuerzoMoral {
        +applyPlayerEffect(board: Board, name: String) Boolean
        +applyComputerEffect(board: Board, name: String) Boolean
    }
    
    %% ==================== CHARACTER PACKAGE ====================
    
    class Player {
        <<trait>>
        +getName() String
        +getSection() String
        +getGems() Int
        +getDeck() Deck
        +getHand() Hand
        +isNpc() Boolean
        +drawCard() Unit
        +setGems(newGems: Int) Unit
        +playMeleeCard(melee: Melee, board: Board) Unit
        +playSiegeCard(siege: Siege, board: Board) Unit
        +playRangedCard(ranged: Ranged, board: Board) Unit
        +playWeatherCard(climateCard: ClimateCard, board: Board) Unit
    }
    
    class AbstractPlayer {
        <<abstract>>
        -name: String
        -section: String
        -gems: Int
        -deck: Deck
        -hand: Hand
        +getName() String
        +getSection() String
        +getGems() Int
        +getDeck() Deck
        +getHand() Hand
        +isNpc() Boolean
        +drawCard() Unit
        +takeDamage(dmg: Int) Unit
        +heal(heal: Int) Unit
        +setGems(newGems: Int) Unit
        +equals(other: Any) Boolean
    }
    
    class Human {
        +name: String
        +gems: Int
        +deck: Deck
        +hand: Hand
        +isNpc() Boolean
        +playMeleeCard(melee: Melee, board: Board) Unit
        +playSiegeCard(siege: Siege, board: Board) Unit
        +playRangedCard(ranged: Ranged, board: Board) Unit
        +playWeatherCard(climateCard: ClimateCard, board: Board) Unit
    }
    
    class Computer {
        +name: String
        +gems: Int
        +deck: Deck
        +hand: Hand
        +isNpc() Boolean
        +playMeleeCard(melee: Melee, board: Board) Unit
        +playSiegeCard(siege: Siege, board: Board) Unit
        +playRangedCard(ranged: Ranged, board: Board) Unit
        +playWeatherCard(climateCard: ClimateCard, board: Board) Unit
    }
    
    %% ==================== BUILD PACKAGE ====================
    
    class Build {
        <<trait>>
        +getCards() List~Card~
        +addCard(card: Card) Unit
        +removeCard(card: Card) Unit
    }
    
    class AbstractBuild {
        <<abstract>>
        -playerBuild: List~Card~
        +getCards() List~Card~
        +addCard(card: Card) Unit
        +removeCard(card: Card) Unit
        +equals(other: Any) Boolean
    }
    
    class Deck {
        +Deck: List~Card~
        +setCards(cards: List~Card~) Unit
        +shuffle() Unit
    }
    
    class Hand {
        +Hand: List~Card~
    }
    
    class Surface~T~ {
        -board: List~T~
        +getCards() List~T~
        +addCard(card: Card) Unit
        +removeCard(card: Card) Unit
        +equals(other: Any) Boolean
    }
    
    %% ==================== BOARD PACKAGE ====================
    
    class Board {
        -siegeZone: SiegeZone
        -meleeZone: MeleeZone
        -rangedZone: RangedZone
        -climateZone: ClimateZone
        +getMeleeZone() MeleeZone
        +getRangedZone() RangedZone
        +getSiegeZone() SiegeZone
        +getWeatherZone() ClimateZone
    }
    
    class Zone~T~ {
        <<trait>>
        +getZone() String
        +getPlayerCards() Surface~T~
        +getComputerCards() Surface~T~
        +playCardComputer(card: T) Unit
        +playCardPlayer(card: T) Unit
    }
    
    class AbstractZone~T~ {
        <<abstract>>
        -zone: String
        -playerCardList: Surface~T~
        -computerCardList: Surface~T~
        +getZone() String
        +getPlayerCards() Surface~T~
        +getComputerCards() Surface~T~
        +playCardComputer(card: T) Unit
        +playCardPlayer(card: T) Unit
        +equals(other: Any) Boolean
    }
    
    class MeleeZone {
        -playerMeleeList: Surface~Melee~
        -computerMeleeList: Surface~Melee~
    }
    
    class RangedZone {
        -playerRangedList: Surface~Ranged~
        -computerRangedList: Surface~Ranged~
    }
    
    class SiegeZone {
        -playerSiegeList: Surface~Siege~
        -computerSiegeList: Surface~Siege~
    }
    
    class ClimateZone {
        -card: Surface~ClimateCard~
        +getPlayerCards() Surface~ClimateCard~
        +getComputerCards() Surface~ClimateCard~
        +playCardComputer(climate: ClimateCard) Unit
        +playCardPlayer(climate: ClimateCard) Unit
    }
    
    %% ==================== CONTROLLER PACKAGE ====================
    
    class GameController {
        -state: GameState
        -observer: ConsoleObserver
        -humanPlayer: Option~Human~
        -computerPlayer: Option~Computer~
        -board: Board
        -currentRound: Int
        -humanPassed: Boolean
        -computerPassed: Boolean
        +setState(newState: GameState) Unit
        +play() Unit
        +start() Unit
        +end() Unit
        +draw() Unit
        +update() Unit
        +win() Unit
        +beginGame(human: Human, computer: Computer) Unit
        +startNewRound() Unit
        +endRound() Unit
        +calculatePlayerStrength() Int
        +calculateComputerStrength() Int
        +playerPass() Unit
        +computerPass() Unit
        +playerHasCards() Boolean
        +computerHasCards() Boolean
        +equals(obj: Any) Boolean
    }
    
    class GameState {
        <<trait>>
        +changeState(newState: GameState) Unit
        +getState() GameState
        +getController() GameController
        +play(context: GameController) Unit
        +start(context: GameController) Unit
        +end(context: GameController) Unit
        +update() Unit
    }
    
    class State {
        <<abstract>>
        +controller: GameController
        +changeState(newState: GameState) Unit
        +getState() GameState
        +getController() GameController
        +play(context: GameController) Unit
        +start(context: GameController) Unit
        +end(context: GameController) Unit
        +update() Unit
        +equals(other: Any) Boolean
    }
    
    class Start {
        +play(context: GameController) Unit
        +start(context: GameController) Unit
        +update() Unit
    }
    
    class PlayerTurn {
        +play(context: GameController) Unit
        +end(context: GameController) Unit
    }
    
    class ComputerTurn {
        +play(context: GameController) Unit
        +end(context: GameController) Unit
    }
    
    class PlayRandom {
        +play(context: GameController) Unit
        +end(context: GameController) Unit
    }
    
    class Play {
        +play(context: GameController) Unit
        +end(context: GameController) Unit
    }
    
    class Win {
        +play(context: GameController) Unit
        +start(context: GameController) Unit
    }
    
    class InvalidTransitionException {
        <<exception>>
        +action: String
    }
    
    %% ==================== OBSERVER PACKAGE ====================
    
    class Observer~T~ {
        <<trait>>
        +update(subject: Subject~T~, response: T) Unit
    }
    
    class ConsoleObserver {
        +update(subject: Subject~String~, response: String) Unit
    }
    
    class Subject~T~ {
        <<trait>>
        +attach(observer: Observer~T~) Unit
        +detach(observer: Observer~T~) Unit
        +notifyObservers(notification: T) Unit
    }
    
    class BaseSubject~T~ {
        -observers: Set~Observer~T~~
        +attach(observer: Observer~T~) Unit
        +detach(observer: Observer~T~) Unit
        +notifyObservers(notification: T) Unit
    }
    
    class ConsoleSubject {
        +run() Unit
    }
    
    class ExitSignal {
        <<exception>>
    }
    
    %% ==================== RELATIONSHIPS ====================
    
    %% Card Hierarchy
    Card <|.. UnitCard
    Card <|.. ClimateCard
    UnitCard <|.. AbstractUnit
    AbstractUnit <|-- Melee
    AbstractUnit <|-- Ranged
    AbstractUnit <|-- Siege
    ClimateCard <|.. AbstractClimate
    AbstractClimate <|-- ClimaDespejado
    AbstractClimate <|-- EscarchaMordiente
    AbstractClimate <|-- LluviaTorrencial
    AbstractClimate <|-- NieblaImpenetrable
    
    %% Effect Hierarchy
    Effect <|.. AbstractEffect
    AbstractEffect <|-- NoEffect
    AbstractEffect <|-- VinculoEstrecho
    AbstractEffect <|-- RefuerzoMoral
    
    %% Player Hierarchy
    Player <|.. AbstractPlayer
    AbstractPlayer <|-- Human
    AbstractPlayer <|-- Computer
    
    %% Build Hierarchy
    Build <|.. AbstractBuild
    AbstractBuild <|-- Deck
    AbstractBuild <|-- Hand
    Build <|.. Surface~T~
    
    %% Zone Hierarchy
    Zone~T~ <|.. AbstractZone~T~
    AbstractZone~T~ <|-- MeleeZone
    AbstractZone~T~ <|-- RangedZone
    AbstractZone~T~ <|-- SiegeZone
    AbstractZone~T~ <|-- ClimateZone
    
    %% State Pattern
    GameState <|.. State
    State <|-- Start
    State <|-- PlayerTurn
    State <|-- ComputerTurn
    State <|-- PlayRandom
    State <|-- Play
    State <|-- Win
    
    %% Observer Pattern
    Observer~T~ <|.. ConsoleObserver
    Subject~T~ <|.. BaseSubject~T~
    BaseSubject~T~ <|-- GameController
    BaseSubject~String~ <|-- ConsoleSubject
    
    %% Composition Relationships
    AbstractUnit *-- Effect
    AbstractPlayer *-- Deck
    AbstractPlayer *-- Hand
    Board *-- MeleeZone
    Board *-- RangedZone
    Board *-- SiegeZone
    Board *-- ClimateZone
    AbstractZone~T~ *-- Surface~T~
    GameController *-- Board
    GameController *-- GameState
    GameController o-- Human
    GameController o-- Computer
    ConsoleSubject o-- Observer~String~
    
    %% Dependencies
    Player ..> Board
    ClimateCard ..> Board
    Effect ..> Board
    Card ..> Human
    Card ..> Computer
    Card ..> Board
    State ..> GameController
Loading

About

Gwentlike game programmend in scala using object oriented programming and design patterns

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages