Tyrant: On a quest to find The Perfect...

46
Tyrant: On a quest to find The Perfect Design Leen De Schutter & Leen Van Gompel June 15, 2005

Transcript of Tyrant: On a quest to find The Perfect...

Page 1: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

Tyrant: On a quest to find The Perfect Design

Leen De Schutter & Leen Van Gompel

June 15, 2005

Page 2: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

Contents

Introduction 4The assignment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4Tyrant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

I Reverse engineering Tyrant 6

1 First steps 71.1 Pattern 3.1: Chat with the Maintainers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.2 Pattern 3.2: Read All the Code in One Hour . . . . . . . . . . . . . . . . . . . . . . . . . 71.3 Pattern 3.3: Skim the Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.4 Pattern 3.4: Interview during Demo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.5 Pattern 3.5: Do a Mock Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2 First project plan 82.1 Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

2.1.1 Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.1.2 Goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.1.3 Criteria for verification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

2.2 Opportunities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.3 Risks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.4 Go-noGo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102.5 Activities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

2.5.1 4/4 to 10/4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102.5.2 11/4 to 17/4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102.5.3 17/4 to . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

3 Other patterns applied after the formulation of the First Project Plan 113.1 Inspecting code coverage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113.2 Running code metrics on Tyrant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113.3 Pattern 5.2: Refactor to Understand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

II Reengineering Tyrant 12

4 Design 134.1 The old design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134.2 The new design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

5 Chronological Overview of Incremental Migration 165.1 Making Sure Things Work And All Tests Succeed . . . . . . . . . . . . . . . . . . . . . . . 165.2 Moving all GUI properties out of QuestApp into a new class ”GUIProperties” . . . . . . . 16

5.2.1 Justification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165.2.2 Information and overview of steps taken . . . . . . . . . . . . . . . . . . . . . . . . 16

5.3 Thread problems when starting multiple instances of Tyrant . . . . . . . . . . . . . . . . . 175.4 Removing Model.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175.5 Moving Runnable methods out of QuestApp, into the new class ”GameRunnable” . . . . 18

5.5.1 Justification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185.5.2 Steps: Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

5.6 Document all things refactored . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185.6.1 GUIProperties.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

5.7 Making Sure Things Work And All Tests Succeed . . . . . . . . . . . . . . . . . . . . . . . 185.8 Extracting pure GUI methods from QuestApp and placing them in MainGUIPanel, as

well as representing them in a separate interface IDisplay that MainGUIPanel implements 185.8.1 Affected methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

1

Page 3: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

5.8.2 Methodical steps: overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195.8.3 Overview per method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195.8.4 Adding the necessary methods to the new interface IDisplay . . . . . . . . . . . . 205.8.5 Performing the actual move: resolving undefined references . . . . . . . . . . . . . 215.8.6 Making Sure Things Work And All Tests Succeed . . . . . . . . . . . . . . . . . . 21

5.9 Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225.10 Moving keyadapter out of QuestApp, into MainGUIPanel . . . . . . . . . . . . . . . . . . 22

5.10.1 Declaration and use . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225.10.2 Plan d’attaque: Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235.10.3 Does it still work? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235.10.4 Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

5.11 Making Sure Things Work And All Tests Succeed . . . . . . . . . . . . . . . . . . . . . . . 235.12 Moving applet-related methods out of QuestApp, into StartApplet . . . . . . . . . . . . . 23

5.12.1 Methods: Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235.12.2 Dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

5.13 Making Sure Things Work And All Tests Succeed . . . . . . . . . . . . . . . . . . . . . . . 245.14 Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245.15 Moving the run() method out of QuestApp, into a separate class GameInitializer . . . . . 24

5.15.1 Method description . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245.15.2 Dependencies of run() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245.15.3 Dependencies of createHero() and gameStart() . . . . . . . . . . . . . . . . . . . . 255.15.4 Executing the move . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265.15.5 Tada: Making Sure Things Work And All Tests Succeed . . . . . . . . . . . . . . . 265.15.6 Never Forget The Importance Of Correct Documentation . . . . . . . . . . . . . . 26

5.16 Designer problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265.16.1 Where did it go wrong . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265.16.2 Detailed description of Designer control flow - 2 phases . . . . . . . . . . . . . . . 275.16.3 What do we know so far? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

5.17 Reworking the design to accomodate Designer . . . . . . . . . . . . . . . . . . . . . . . . . 285.17.1 What’s the problem? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285.17.2 So what’s the new design? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

5.18 Implementing the new design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295.18.1 Splitting up Designer - Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295.18.2 Actual implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295.18.3 Is Designer fixed? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295.18.4 Making Sure Things Still Work And All Tests Succeed . . . . . . . . . . . . . . . . 30

5.19 Making Sure Things Work And All Tests Succeed . . . . . . . . . . . . . . . . . . . . . . . 305.20 Moving QuestApp.gameOver() and QuestApp.getDeathString() to Game . . . . . . . . . 30

5.20.1 Justification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305.20.2 Information and overview of steps taken . . . . . . . . . . . . . . . . . . . . . . . . 30

5.21 Moving QuestApp.fileEncoding to Game . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305.21.1 Justification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305.21.2 Information and overview of steps taken . . . . . . . . . . . . . . . . . . . . . . . . 31

5.22 Deleting QuestApp and QuestApplication . . . . . . . . . . . . . . . . . . . . . . . . . . . 315.22.1 Justification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

5.23 Information and overview of steps taken . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

6 Overview of difficulties encountered 326.1 GUI Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326.2 On the accidental breaking of Designer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326.3 On design changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326.4 On the work that has not been done . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

III Appendices 34

2

Page 4: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

A Code review 35

B Documentation verification 37

C Installation guide 40

D Refactor to understand 41

E Clover coverage guide 42

F Metrics results for Tyrant 43

3

Page 5: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

Introduction

This document functions as a manual of sorts that allows the reader to understand the steps we took incompleting this assignment. First our choice to reengineer Tyrant is explained, followed by a descriptionof the First Steps we’ve taken preparing for the actual refactoring work. This description includes theresults we gained from using the patterns proposed in [5]. A description of our First Project Plan isincluded as well.In the next part, Reengineering, the new design we introduced is compared to the old, and its advantagesare put forth. Following that a Chronological Overview of the incremental migration steps we took isgiven, and finally some of the difficulties we encountered while reengineering Tyrant are presented, alongwith how we handled them.

The assignment

Redesign an existing software system so that new requirements can be implemented more easily. Noextra functionality needs to be implemented; however the existing design should be adapted in such away that adding new functionality becomes very simple.

Result criteria

To show you succeeded in performing this assignment, you will need to demonstrate the following prop-erties:

1. The reengineering operation is behaviour-preserving:

(a) You are able to demonstrate the mapping between each of the classes in the original design,and the classes in the new design.

(b) The compilation process runs flawlessly.

(c) The tests run without errors.

(d) Tyrant starts flawlessly.

2. The new design improves overall quality:

(a) GUI and Game functionality have been decoupled.

(b) Substituting or adding a new graphical interface is easy and involves modifying no codecontained in Game classes.

Tyrant

Instead of reengineering ArgoUML as originally proposed by LORE, we chose to reengineer a differentproject for the following reasons:

• Accepting ArgoUML for our reengineering assignment implies that the opportunity to experiencethe entire reengineering process is lost to us. For example, the Go/No Go-decision would not betaken in the case of ArgoUML, even though this is a crucial part of any reengineering task.

• Sense of purpose: in our impression, refactoring ArgoUML will not result in anyone using our codein practice. ArgoUML developers already have a clear view of a new and improved design. Thisdecreases the chances they’ll adopt ours.

• We want to use the opportunity to have design discussions with our peers to the fullest, yet stillfollow our own path in the reengineering process. By choosing a different project with differentdesign problems, we avoid so-called ”contamination” of design results.

4

Page 6: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

• As mentioned in [5], clear and easy communication with the current maintainers is a must. How-ever, since different reengineering groups will already be working on ArgoUML, this opportunity ispartly lost, since the developers will need to divide what little time they have between all reengi-neering groups. Moreover Tyrant’s developer group, in particular Tyrant’s project administrator,has already shown interest in the reengineering project, and has made several helpful commentsindicating he will fully support the reengineering process. This substantially improves our chancesof succeeding in our task.

• We have very little experience with Tyrant’s problem domain (Tyrant is a Role Playing Game).Our interest in the domain is therefore also a factor in deciding to reengineer Tyrant.

5

Page 7: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

Part I

Reverse engineering Tyrant

6

Page 8: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

1 First steps

The first part of any reengineering operation consists of understanding what’s there. ”What’s there”includes documentation, executable code, comments in code, code structure, code traces, etc.We used the book [5] as a guide. The patterns proposed in the book describe the steps one can takewhen aspiring to reengineer an application.We did not follow the book rigirously, but rather picked those patterns that were within our grasp,time-efficient, and seemed useful. In what follows the patterns we used are described, along with theresults gained from applying them to Tyrant.

1.1 Pattern 3.1: Chat with the Maintainers

The project administrator, from hereon referred to as Mike (user name mikera) provided extensive in-formation on so-called culprit classes. Other developers as well gave feedback and stated their supportfor the project. Based on this information we identified three classes that mixed GUI and Game func-tionality: QuestApp, GameScreen, and Game.Records of our ”chats with the maintainers” can be found on the Tyrant forum [4].

1.2 Pattern 3.2: Read All the Code in One Hour

In order to get a first impression of the coding style, comment coverage, and code anomalies we reviewedthe code in an hour. This code review has been included in the Appendix (see A). It appears thatcode comments are correctly used (both brief and correct), coding conventions are rarely adhered to andvariable naming is often vague (think along the lines of Thing, Stuff, etc).

1.3 Pattern 3.3: Skim the Documentation

We reviewed the extent and correctness of external documentation (i.e. no code comments). As before,the appendix contains the results of the documentation review (see B). From the review we concludedthat most external documentation is correct if undated, that Javadoc comments are few and far betweenindeed, and that documentation on past design choices is available.

1.4 Pattern 3.4: Interview during Demo

In view of the fact that we did not have an opportunity to contact Tyrant users (specifically: users notinvolved in developing Tyrant) we skipped this pattern. Moreover it was not judged very useful in thecontext of our reengineering task, since no outward changes needed to be made, and thus the users wouldnot be affected.

1.5 Pattern 3.5: Do a Mock Installation

This pattern was executed, and resulted in an Installation guide that was written (an installation guidefor developers, that is - the game itself was very easy to download and play). The installation guide canbe found in the appendix as well (see C).

The application of these patterns led to our First Project Plan.

7

Page 9: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

2 First project plan

2.1 Scope

2.1.1 Context

“Tyrant is a game of heroic adventure, where you pit your wits and cunning against a fearsome array ofmonsters and fiendish puzzle in your attempt to gain fame and fortune.

Tyrant is a Roguelike Game in the tradition of many other great games such as the original Rogue,Nethack, Angband and ADOM. These games emphasize addictive gameplay and randomly generateduniverses to create an immersive and imaginative game that looks deceptively simple yet contains agreat deal of detail and strategic depth.”[1]

Tyrant has a Graphical User Interface (GUI) that is entirely controlled using keyboard inputs. Thereason for this decision lies in the nature of the game: (developers’s explanation) “It would take hoursto get anywhere if you had to point and click with the mouse everytime you wanted to do something.Keys are much faster, and once you get into some serious adventuring they will begin to feel like secondnature.”[3]

2.1.2 Goals

The main goal is to cleanly separate the GUI functionality from the game Engine.In order to do this, Mike (mikera, Tyrant’s lead developer) proposed these changes:

• Simplify the GUI code so that it is easier to extend in future

• Break down screens into smaller GUI components with appropriate layouts instead of renderingeverything with Component.paint(..) etc. Exception is the MapPanel, which we want to continueto render in one double-buffered shot for performance reasons.

• Reduce elements of duplication and redundant code. InventoryScreen and InventoryPanel areprobably the biggest culprits here.

• Absolutely no need for someone writing game logic to concern themselves with the GUI. Currentlythis is *mostly* the case, but there are some complexities in areas where screen switching isinvloved.

It is possible that other reengineering opportunities arise as we are working on the code, but this is thebase specification.

2.1.3 Criteria for verification

The reengineering task should first and foremost be behaviour-preserving; in order to verify this, thefollowing criteria must be met:

• A mapping between the classes in the original design and the new one can be demonstrated.

• The program compiles without errors

• The tests run flawlessly

• Tyrant starts without errors

The reengineering effort should improve the overall quality of Tyrant:

• Unnecessary dependencies from the Engine to the GUI have been minimised or eliminated; asolution has been found for dependency introduced by the need for screen switching.

8

Page 10: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

• Extensibility of the GUI has been improved:

• Behaviour has been moved closer to the data

• Interfaces are clearly defined

• Modularity of the GUI has been improved by splitting up large GUI classes into smaller, moremanageable modules

• Duplicated and/or redundant code has been minimised, especially in InventoryScreen and Inven-toryPanel

Our questions:

1. Are these well-formed criteria?

2. Are we taking on too much work considering the time available? Could we solve this by settingpriorities?

2.2 Opportunities

1. Communication with the developers is possible due to the presence of a developers’ forum. Theforum is frequently read by the developers and provides an easy way to communicate. We alreadynoticed that developers respond to our posts on this forum. In particular Mike (mikera, the projectadministrator) responds very quickly and thoroughly. Reengineering Tyrant seems to be his petproject. This might be the reason why he is so willing to help us.

2. Tyrant test coverage is about 60%[2]. This seems like a safe test base.

3. Documentation is extensive and seems up to date. Reasons:

(a) All documentation is written by and for developers and seems to be used extensively.(b) Parts of the documentation have been verified with the code, and most of it is correct. Incon-

sistencies that were detected, seem to be things that still need to be implemented, as opposedto being signs of a restructured design.

(c) Mike’s explanation of the use of the ?Thing? class includes a description of Tyrant’s designevolution. This indicates that the documentation has at least partly been kept up to date.

4. Tyrant is developed in Java, a language we are familiar with.

5. Eclipse has been used for Tyrant development. This makes things easier in the sense that theprogram was written in an environment we’re used to. As a consequence we are already familiarwith some files/conventions that we would otherwise need to discover. For example, unfinishedtasks are interesting from a reengineering perspective. In eclipse these are marked with TODOand automatically listed. Because Tyrant is developed using eclipse we can easily get an overviewof how many tasks are still unfinished, in which part of the code most tasks appear, etc.

2.3 Risks

1. Reading and understanding Tyrant’s code will be harder because of the lack of coding conventions.

2. There is hardly any comments in the code. As a consequence javadoc documentation can begenerated but is hardly extended by any extra developers information.

3. We will have at least some extra trouble understanding the code because we are not familiar withthe problem domain.

4. Our lack of experience on reengineering projects will delay our progress significantly. The qualityof our work will be adversely affected as well.

9

Page 11: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

2.4 Go-noGo

Go!

Because...

1. We have got confidence in the developer community, especially Mike.

2. Although there is a lack of documentation within the code, there is a lot of external documentationon the Tyrant website.

3. Although no coding conventions have been used and we know very little about RPG gaming,understanding the code will be feasible because of our knowledge of Java.

4. We can be relatively certain that if we break something in the code, we will notice it, because thetest base covers the code for about 60%.

5. The only way to build up experience is by actually reengineering a project. Tyrant provides anideal opportunity for two reasons. First of all, the developers have shown themselves to be verysupportive. Second of all, our teachers at school will guide us if problems arise. These peopleprovide a safety net that makes it easier to perform our first reengineering project.

2.5 Activities

2.5.1 4/4 to 10/4

Speculate about Design (Pattern 4.2, [5])We will generate the design from Tyrant using tools. After that, we will refine it, if necessary. To achievethis we will both search a tool and try it seperately. Afterwards we will discuss the results.

1. Finding an appropriate tool and extracting the design: 6/4

2. Discussing the results: 6/4

3. Refine the design: if necessary

Study the Exceptional Entities (Pattern 4.3, [5])We will further explore the code using metrics. We will start computing the metrics from the book andthe lab session and make a top 3. This should be finished at 11/4. We will do this seperately and thencompare the results.

Reading of chapter 5This must be done by 11/4.

2.5.2 11/4 to 17/4

Tie Code and Questions (Pattern 5.1, [5])Mark questions about/in the code.

Refactor to Understand (Pattern 5.2, [5])

Step through the Execution (Pattern 5.3, [5])Understanding how objects cooperate by running the program. Maybe Hyades can be used.

Look for the Contracts (Pattern 5.4, [5])Study interfaces.

Learn from the Past (Pattern 5.5, [5])Why is the system as it is? (e.g. Thing class)

10

Page 12: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

2.5.3 17/4 to . . .

Reengineering

3 Other patterns applied after the formulation of the FirstProject Plan

3.1 Inspecting code coverage

A Clover code coverage report was generated. A guide to making Clover work with Tyrant can be foundin the appendix (it involved some peculiar steps to get this to work, see E).Tyrant has 201 JUnit tests. All tests pass. Mean code coverage is 54,4%. Code coverage for the GUIclasses is extremely low. View the results at http://www.vanhoof.dyndns.org/ sre/03-22/report/index.html.

3.2 Running code metrics on Tyrant

Results are once again presented in their entirety in the appendix (see F). It appears that the screenclasses are deeply nested in the inheritance hierarchy. GameScreen, one of the classes indicated by Mikeas being a mixed GUI-Game class, is the third largest class with respect to number of methods.

3.3 Pattern 5.2: Refactor to Understand

Findings resulting from this pattern’s application can be found in the appendix (see D).

11

Page 13: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

Part II

Reengineering Tyrant

12

Page 14: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

4 Design

4.1 The old design

Tyrant can be started in 2 modes: Applet and Application mode. To reuse as much of the GUI codeas possible, Tyrant developers chose a design in which the Application mode reused the applet code(contained in QuestApp) by adding the applet to a Frame. However, due to tight coupling of GUI andimplementation functionality in QuestApp (both inherent to applets, and caused by implementationfunctionality implemented in QuestApp as well) this was an unsatisfactory solution.The old design can be seen in Figure 1. Blue color indicates Game functionality, whereas orange coloredelements are (nearly) pure GUI elements. As can be seen from the figure, QuestApp incorporated anearly balanced mix of Game and GUI functionality, which is undesirable. Moreover (though this is notseen on the figure due to the fact that QuestApps numerous static properties and methods - 20+ - couldnot be generated in the diagram) QuestApp was a very large God class.

Figure 1: Old Design: QuestApp and QuestApplication

4.2 The new design

Without going into too much detail as to how we finally conceived the new design (most of which canbe found in the appendix and the Chronological Overview), the new design is presented here.

Whereas previously the Application mode was executed by QuestApplication, a class that initializedboth Game and GUI properties and then used QuestApp to paint its GUI, and the Applet mode wasstarted in QuestApp, the new design provides two start entries called StartApplication and StartApplet.StartApplication creates an ApplicationGUI (a class that takes care of painting the GUI by instantiatingan interface called IDisplay and using this IDisplay to compose part of its frame), and StartApplet di-rectly creates this IDisplay interface. Both classes then create a GameInitializer and a GameController.The latter uses this GameInitializer to kick-start the Game thread.

13

Page 15: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

The IDisplay interface previously mentioned is implemented by MainGUIPanel. This class contains mostGUI functionality previously contained in QuestApp itself, and is instantiated both by StartApplet andStartApplication (so that the GUI code is still reused). The latter instantiates MainGUIPanel indirectlythough through ApplicationGUI, a wrapper that takes care of creating the surrounding frame and such.The new design is presented in Figure 2. It is clear that QuestApp’s functionality has been cleanlydistributed over classes with a clear purpose in all except one case. GameInitializer still contains a mixof GUI and Game functionality (although the proportion of Game functionality in GameInitializer isnow much smaller than before). GameInitializer was not split because it was thought unnecessary. It isa reasonably small class, and it does not directly depend on the GUI’s implementation through the useof the IDisplay interface.

14

Page 16: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

Figure 2: New Design: QuestApp and QuestApplication15

Page 17: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

5 Chronological Overview of Incremental Migration

5.1 Making Sure Things Work And All Tests Succeed

1. Starting Tyrant: OK

2. Running all tests:

• Out of the existing test base: 6 fail (3 for ThingMaker, 3 for MapMaker)

• Out of our new test base: 1 exception that seems like it shouldn’t have been there (the testdoes not fail however)

3. First: fix our own GUI testConclusion: nothing to be fixed. The exception is thrown and a stacktrace printed in the gamecode itself. We probably shouldn’t change that behavior. Therefore the test case remains, but willalways output an exception

4. Next: fix existing tests. Tests that fail:

• mikera.tyrant.test.ThingMaker TC:

– TestStoring– TestStoringIs– testThings modified

All fixed by using StringBuffers instead of Strings and, more importantly, MapMaker.NLinstead of a hardcoded windows-specific carriage return.

• mikera.tyrant.test.MapMaker TC:

– testStore– testStore2– testStore3

Same solution as with ThingMaker TC

All fixes have been marked with ”//TODO” so that, in case the solutions are not desirable for theTyrant developers, they can easily be found and modified.

5.2 Moving all GUI properties out of QuestApp into a new class ”GUIProp-erties”

5.2.1 Justification

As QuestApp was a large class with mixed responsibilities, the goal is to phase out QuestApp, and todivide its responsibilities between classes with clear goals. The static properties contained in QuestAppthat are purely GUI related should be isolated, as they are nothing more than simple properties thatneed to be accessed by a number of classes. These properties will therefore be isolated in an aptly namedclass: GUIProperties

5.2.2 Information and overview of steps taken

1. Affected parts in QuestApp: all COLOURs, all images, the static initializer code, method ”getIm-age”.

2. Other classes modified to support this change:

• ArtScreen

• CharacterScreen

16

Page 18: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

• CLabel

• DetailedListScreen

• GameScreen

• GameTile

• ImageGadget

• InfoScreen

• InventoryPanel

• InventoryScreen

• ListScreen

• MapPanel

• MessagePanel

• Screen

• StatusPanel

• TextZone

• TitleScreen

• TPanel

• Designer

• ThingEditor

3. First check: Tyrant starts OK

4. Second check: All tests succeed!

5. Changes committed

5.3 Thread problems when starting multiple instances of Tyrant

The new test case involves starting up multiple copies of Tyrant one after another. This gives rise toNullPointerExceptions, due to key events not being sent to the correct frame. Since at present we don’tknow how to terminate a Tyrant instance before the whole test case is finished (and therefore, multiplewindows simultaneously exist), this means that the test case that aggregated several startup scenariosmust be split up into several test cases.The error only occurred when all tests (i.e., all tests in the old test base together with the new test caseinvolving several GUI frames) were executed one after another; running the GUI tests separately doesnot pose any problem.

However, considering the startup scenarios involving variations on the ”load saved game”-theme arenot really GUI-related and thus are not really involved in the refactoring operation, it is simpler justto remove all but the normal application startup scenario. That scenario however should be testedthoroughly, since it will be severely affected by our refactorings.

5.4 Removing Model.java

This class only stored the main font, which has been isolated along with other properties, into theGUIProperties class. The Model class has been deleted, along with all references in the controllers.

17

Page 19: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

5.5 Moving Runnable methods out of QuestApp, into the new class ”GameRunnable”

5.5.1 Justification

The method required by the Runnable interface that QuestApp implements (namely run()), is used todo initial game start-up. Loading a library of creatures, presenting the user with a choice to restore orcreate a game, etc. After all has been set up, the method mainLoop() in GameScreen is called, whichcontains the actual game loop consisting of checking for input, delegating that input, etc. However,this run() method should be isolated to a separate class since it has a very specific function of its own:checking startup conditions and loading appropriate files. The name GameInitializer is proposed toindicate that function. Since the method is tightly coupled to all methods in the class Game that takecare of creating, restoring and saving games, the latter methods will be moved into this class as well(this will be done later on though; for now, only QuestApp is being refactored).

5.5.2 Steps: Overview

1. QuestApp’s run() method is a tough mix of both engine related functionality (createHero(),Lib.asynchronousCreate()) and GUI-related (repaint(), switchScreen(), etc). Therefore this task isdeferred to a later time, when critical GUI-functions have been moved from QuestApp to Main-GUIPanel.

5.6 Document all things refactored

5.6.1 GUIProperties.java

Documented.

5.7 Making Sure Things Work And All Tests Succeed

Tyrant start-up: OKTests: OK

5.8 Extracting pure GUI methods from QuestApp and placing them inMainGUIPanel, as well as representing them in a separate interface IDis-play that MainGUIPanel implements

5.8.1 Affected methods

• public boolean isGameScreen()Tests whether the currently displayed screen is the game screen or something else (an info screen,a detailed list screen, etc).

• public void switchScreen(Component s)Discards the currently displayed screen, displaying the new component s instead.

• public void setupScreen()Creates a new game screen or refreshes it by clearing the message panel, then displays it.

• public void setScreen(GameScreen screen)Changes the internally kept GameScreen object to screen.

• public GameScreen getScreen()Returns the internally kept GameScreen.

18

Page 20: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

5.8.2 Methodical steps: overview

The above methods will be moved to MainGUIPanel in a structured way:

1. Check for QuestApp properties used in the method

2. Determine if those properties can be moved to MainGUIPanel along with the method, and if so,which other methods depend on them. If there are such methods, determine whether they can bemoved to MainGUIPanel as well, check their properties, etc. until the bottom of the recursion isfound.

3. Find all methods that depend on the method(s) that will be refactored. Determine what shouldbe done with them.

4. Add the method/these methods to the interface (one of the interfaces) that MainGUIPanel imple-ments.

5. Move the implementation of the method(s) to MainGUIPanel, possibly along with affected prop-erties.

6. Every time a separate block has been moved, rerun all tests.

5.8.3 Overview per method

• public boolean isGameScreen()

1. QuestApp properties used: screen, mainComponent

2. Methods that depend on screen, mainComponent:

– screen: used in getScreen(), setScreen(GameScreen screen)∗ getScreen(): Only uses screen∗ setScreen(GameScreen screen): Only uses screen∗ End of recursion

– mainComponent : used in switchScreen(Component s)∗ switchScreen(Component s):

· QuestApp properties used: mainComponent· End of recursion

3. Methods that depend on isGameScreen(), getScreen(), setScreen(), and switchScreen():

– Methods depending on isGameScreen()

∗ Game.getInput(): can be temporarily resolved by giving Game an instance of the inter-face MainGUIPanel implements, and having Game call mainGUIPanel.isGameScreen()instead of questApp.isGameScreen().

– Methods depending on getScreen()

∗ In Game: getDirection(), getMapPanel(), ...: can be resolved analogously as before∗ In QuestApp: gameOver(), gameStart(), run(), setupScreen()∗ In RangedWeapon: fire(...): accesses getScreen() through the getQuestApp() method

of Game. Can be temporarily resolved by making an accessor method in Game thatreturns the interface that MainGUIPanel implements.

∗ In Recipe: apply(): same situation as above∗ In Scroll : Accesses getScreen() by calling QuestApp.getInstance().getScreen(): can be

temporarily resolved by replacing it with a call to Game.getGUIInterface().getScreen()(or something like that).

19

Page 21: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

∗ In WandScript(): exact same situation as above– Methods depending on setScreen()

∗ In GameScreen: The constructor invokes setScreen(this); can be resolved by passingthe interface that MainGUIPanel implements to this constructor - however it is in-voked in three classes: KillAllBaddies, MoreKilling, and QuestApp. The former twodo not have access to the interface MainGUIPanel will implement (and they need thisin order to pass it to the constructor of GameScreen). These are performance testingclasses. Second option: make the temporary method Game.getIDisplay() static, inwhich case GameScreen’s constructor does not need access to a questapp/gui object,and the refactoring stops there. This is the ugliest but most effective fix, and since itis but temporary, this method will be chosen.

∗ In QuestApp itself : setupScreen(). This method will be moved along with setScreen(),and thus should not present problems.

– Methods depending on switchScreen()

∗ In Game:· infoScreen()· scrollTextScreen()· selectItem(), selectSaleItem(), selectSaleNumber(), selectString()· showData()All can be adapted to use an instance of the interface MainGUIPanel will implement,that is passed to Game by invoking a method setIDisplay (temporary).

∗ In GameScreen:· castSpell· doDebugKeys· doEat· ...Will be temporarily fixed by making the temporary method Game.getGUIInterface()

∗ In QuestApp:· createHero()· run()· setupScreen()The first two will not be moved immediately, as they contain entwined GUI andEngine functionality. They will need access to the switchScreen method, which willbe accomplished by invoking Game.getGUIInterface().switchScreen (for now!). Thethird method, setupScreen(), will be moved along with switchScreen().

• public void switchScreen(Component s)Properties used in switchScreen: mainComponent, which will be moved along with switchScreen().

• public void setupScreen()Properties used by setupScreen: screen, which will be moved along with setupScreen().

• public void setScreen(GameScreen screen)Analogous.

• public GameScreen getScreen()Analogous.

5.8.4 Adding the necessary methods to the new interface IDisplay

All methods that will be moved in this refactoring operation are interdependent and need to be aggregatedin the same interface. Therefore the interface IDisplay will contain all of the aforementioned methods,

20

Page 22: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

with identical names so as to remain as consistent as possible with the old code base (thus minimizingthe learning curve for the Tyrant developers).

• public boolean isGameScreen()

• public void switchScreen(Component s)

• public void setupScreen()

• public void setScreen(GameScreen screen)

• public GameScreen getScreen()

5.8.5 Performing the actual move: resolving undefined references

In the end, all unresolved references were solved by: - Letting Game keep an instance of IDisplay (theinterface containing all methods that were moved), implemented by MainGUIPanel. Game is giventhis instance in the init() method of TController. - Having a getDisplayInterface() method in Game(static!) so that all other objects that needed the methods can access this interface. This getter-methodis temporary, and should later be removed as it breaks the principle of what we’re trying to do here -namely separating GUI and Engine - but for the purposes of breaking down the refactoring into small,reversible steps, it’s okay.

5.8.6 Making Sure Things Work And All Tests Succeed

Tyrant startup: OKTyrant tests: 3/213 tests fail!

• Game TC: testOption

• GameHandler TC: testRunOffEdge and testRunningOverMessagePoint

They all fail in a similar manner: NullPointerExceptions when invoking Game.getMapPanel(). The of-fending statement in that method: fGUI.getScreen(). Apparently, fGUI is null at that point (somethingthat should not happen, obviously).Moved the statement Game.setDisplayInterface(gui) in the init() of TController a bit up. Rerunningtests. Same result.Going in deeper: Inspecting the code that causes the NullPointerException: Previously QuestApp wasused to invoke getScreen() on, but now it is fGUI. There was a null check on QuestApp, but that nullcheck did not exist for fGUI. Moreover, QuestApp was constructed in such a way that, if an instanceof it was retrieved using the getter method getInstance, a new instance was always created if necessary(thus QuestApp was never really null).Possible solution: placing an analogous check on fGUI in the method getMappanel() in Game. Pro/contra:

• PROAlthough the full ramifications of simply placing an analogous check on fGUI in Game (i.e., notinvoking getScreen() if fGUI is null) are not understood, it seems likely that this such a check willsolve the problem without creating others.

• PROThe tests that fail do not really do anything with the GUI, but rather with the input handlers.Therefore the chances of having this additional line break anything are small.

• CONTRAOn the other hand, when looking at the Clover coverage report generated before refactorings, itseems that none of the tests executed this particular scenario (which is quite logical, since quest appwas never null, as opposed to fGUI apparently). Therefore, it may be possible that this untestedscenario will occur in the future and be caused by this refactoring.

21

Page 23: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

However, I estimate the chances of fGUI being null outside of a test environment very small to non-existing. Therefore I’m going to go ahead and try it.Result: ALL TESTS SUCCEED.Putting the following line in Game.getMapPanel() solved the problem:

if (fGUI==null) return null;

5.9 Documentation

IDisplay has been documented. Back-documentation of MainGUIPanel, StartApplication and Star-tApplet done. Javadoc generated for all tyrant packages + all referenced jars (a LOT! beware whensynch’ing!)

5.10 Moving keyadapter out of QuestApp, into MainGUIPanel

5.10.1 Declaration and use

keyadapter is declared as follows:

public final KeyAdapter keyadapter = new KeyAdapter() {public void keyPressed(KeyEvent e) {// call the currently registered keyhandlerif (keyhandler != null) {keyhandler.keyPressed(e);} else {Game.getUserinterface().go(e);}}};

It is not used in QuestApp, but it is used by the following classes: In mikera.tyrant.author: DesignerFor now, a call to Game.getDisplayInterface().getKeyAdapter() and getKeyHandler() should suffice.However, this class should be checked thoroughly because I suspect that it has been brokenby our refactorings. Checking the coverage report from before: indeed, Designer was covered for only7,7% which is next to nothing. Ouch. In mikera.tyrant:

• DetailedListScreen: in the constructor

• Game: in scrollTextScreen() and showData()

• InventoryPanel: selfHookKeyEvents()

• ListScreen: constructor

• MainGUIPanel: init()

• MapPanel: constructor

• TPanel: constructor

The toughest of these to tackle would be Game and InventoryPanel. The rest can be solved by replacingthe constructor argument that is now QuestApp, with keyadapter.GameIn scrollTextScreen(), Game creates a new Screen, and adds keyadapter to it. However, what Game reallyshould do is defer the creation of screens, as well as adding keyadapter to it, to the GUI. In showData()essentially the same thing happens.

22

Page 24: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

InventoryPanel In the method selfHookKeyEvents() keyadapter is only needed to remove it from Inven-toryPanel, so it can be replaced with another adapter. For this reason it may be best to make keyadaptera protected member of TPanel, that is initialized in the constructor. Since InventoryPanel indirectlyinherits from TPanel, this would enable InventoryPanel to remove it.As for Game needing access to the keyadapter so it can construct its own screens and link keyadapter tothem: this really should not happen. Therefore the code used to construct these screens will be extractedto a method in MainGUIPanel.

5.10.2 Plan d’attaque: Overview

1. Move keyadapter to MainGUIPanel; it will be a private member.

2. Replace QuestApp argument in Screen/Panel constructors with this KeyAdapter; make it a pro-tected member in TPanel so InventoryPanel can access it.

3. Refactor the Game code so that it calls createScreen(...) on IDisplay, instead of creating screensand linking them with keyadapter by itself. For this to succeed, scrollTextScreen() and showData()must be moved to MainGUIPanel, which is possible because only Game and GameScreen dependon them. GameScreen will temporarily call Game.getDisplayInterface() to access these functions;afterwards that class too will be refactored, so it doesn’t need this detour anymore.

5.10.3 Does it still work?

1. After all refactorings except the extraction of the Game methods scrollTextScreen() and show-Data():

• Tyrant startup: OK

• Running the tests: All tests succeed!

2. The Game methods will be refactored at a later date; first QuestApp should be finished.

5.10.4 Documentation

Added documentation in IDisplay and MainGUIPanel for the new members and methods.

5.11 Making Sure Things Work And All Tests Succeed

Tyrant start-up: OKTests: OK

5.12 Moving applet-related methods out of QuestApp, into StartApplet

5.12.1 Methods: Overview

The methods that need to be moved:

• init(), init(Runnable)

• stop()

• destroy()

The constructor of StartApplet must also call super(), so that the applet is properly initialized.

23

Page 25: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

5.12.2 Dependencies

Other methods depending on these methods: noneProperties used by these methods: noneMethods called by these methods: setInstance(null) on questApp (can be transferred to startApplet)

5.13 Making Sure Things Work And All Tests Succeed

Tyrant start-up:

• Applet: OK

• Application: OK

Tests: OK

5.14 Documentation

All methods that were moved have been Javadoc’d.

5.15 Moving the run() method out of QuestApp, into a separate class GameIni-tializer

5.15.1 Method description

The run() method initializes several GUI and Engine properties to prepare for actual game startup.Among others it presents a startup screen where the user can choose to restore or create a game, playthe game in debug mode, etc. After checking and making sure all resources needed to accomodatethe user’s choice are loaded, the run() method defers control to the method mainLoop(), contained inGameScreen. That loop is the main game loop that keeps the game running, checks for input andwhether the hero died, etc.

5.15.2 Dependencies of run()

1. QuestApp properties used by run():

• isapplet

• gameFileFromCommandLine

Both can be solved by having GameInitializer ’s constructor take both properties as parameters.Other relevant methods that depend on QuestApp.isapplet : Game.save(), Game.restore(), andGame.saveMap(). The only reason they need access to this variable is to warn the user that anunsuccessful save/restore operation may be due to browser restrictions. For now, this field may bestored in Game just as well. Later on, when Game is refactored, it will be phased out along withother properties and methods.Other methods that depend on QuestApp.gameFileFromCommandLine: no relevant ones (one inQuestApplication, but that class is not used anymore).

2. Methods used by run():

24

Page 26: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

• Several methods that are invoked on Game.getDisplayAdapter():run() is tightly coupled with GUI functionality. Since it will be instantiated in TController(that is able to access the GUI through getContainer()), it may be best to hand an IDisplayinstance over to GameInitializer in the constructor.GameInitializer.run() needs to access Game functionality as well. Therefore an interfaceIEngine implemented by Game is in order. However, currently nearly all functionality providedby Game is accessible statically. This means that the introduction of the IEngine interfacecan be postponed to later in the reengineering process. That way we keep our steps small andmanageable.

• repaint():This method is invoked on the applet that is extended by QuestApp. One way or another thismust be implemented by IDisplay as well, for the screen to be refreshed. Thus the additionof a repaint() method in the IDisplay interface is necessary.

• QuestApp.gameStart():This is functionality that forms an integral part of GameInitializer’s purpose, namely kick-starting the game and entering a map. Therefore it is decided to move gameStart() intoGameInitializer as well. Dependencies of gameStart() will be reviewed in a later section.

• QuestApp.createHero()This method does a lot of things: it creates a screen to ask for the hero’s race, then processesthe input, creates another screen to ask for the hero’s profession, processes the input, andfinally asks for the hero’s name. A hero is created using the input. It makes sense to movethis method either into the GameInitializer class, or in a separate class of its own. However,in view of the size of the method, the latter may be overkill. Therefore the former method ischosen, and dependencies will be discussed in a later section.

3. Methods depending on run():run() will only be indirectly invoked when a GameInitializer instance is used to create a thread.This happens in two classes: TController (a new class created during refactoring), and Designer(the class that is currently broken). Whereas TController forms no problem, as it has access toboth an IDisplay instance as well as an IEngine instance, Designer is an entirely different matter.Essentially, the Designer class redefines input handlers and screens so as to support a ”DesignerMode” in which authors can edit maps. Since all this refactoring that we’re doing is mainly basedon moving GUI-based functionality around, the Designer class will need to be severely refactoredin order to work again. This is the next topic on the agenda, and will be addressed in the nextsubsection. For now, a temporary (non-working but compiling) workaround will be made.

5.15.3 Dependencies of createHero() and gameStart()

QuestApp.createHero():

1. Properties used by createHero():None

2. Methods used by createHero():The method calls made by createHero() are either static method calls (and thus accessible fromanywhere), or GUI-related calls (these are not a problem since GameInitializer will be providedwith an IDisplay instance when it is constructed).

3. Methods depending on createHero():Only QuestApp.run(), and that method is moved along with createHero()

QuestApp.gameStart():

1. Properties used by gameStart():this: an instance of quest app. As this is only used to call Game.setQuestApp(), which in turn

25

Page 27: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

invokes QuestApp.setInstance(), which (at that very moment) should already be referencing this,this line can be removed.

2. Methods used by gameStart():Static methods from the Game class, as well as GUI-related functions that will be accessible throughthe IDisplay instance passed along to GameInitializer. No problems here.

3. Methods depending on gameStart():Only QuestApp.run(), and that method is moved along with createHero()

5.15.4 Executing the move

As apparent from the previous discussion, in total 3 methods need to be moved: run(), gameStart(), andcreateHero(). The properties that need to be moved: isapplet and gameFileFromCommandLine (bothwill be constructor parameters along with the IDisplay interface). Additionally a method repaint() willneed to be created in the IDisplay interface.

5.15.5 Tada: Making Sure Things Work And All Tests Succeed

Tyrant start-up:

• Applet: A minor issue presented itself when stopping the applet. It seems we erroneously putthe stop() method in StartApplet, but the Game thread needed to be cancelled in GameInitializerinstead of in StartApplet. Now: OK

• Application: OK

Tests: OK - all tests succeed

5.15.6 Never Forget The Importance Of Correct Documentation

GameInitializer, the adapted methods accomodating gameFileFromCommandLine and isapplet in Main-GUIPanel, startApplet, StartApplication and TController, and the added method in IDisplay have beenJavadoc’d.

5.16 Designer problems

How to start the Designer class: this is done by pressing e in the initial startup screen (in embeddedmode), or by starting it seperately. Worst fears confirmed: Designer indeed doesn’t work anymore.Fixing it won’t be easy.

5.16.1 Where did it go wrong

The designer class is a special class, in the sense that does not reside in the normal mikera.tyrant package,but in a seperate mikera.tyrant.author package. Actually, what it does is redefine input handlers andGUI so as to allow the creation of maps, along with assorted creatures and items, from within the actualTyrant application. If the users selects a special option at game startup, the designer mode is entered,and all the usual handlers and GUI elements are replaced with elements necessary for map creation.While this is in and of itself a handy tool, it does rely on the redefinition of graphical elements - andthese were moved to other classes by us. The problem wasn’t detected earlier because the Designer classhas but few tests.

26

Page 28: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

However, this is a unique opportunity for us to show the advantages of the new design we introduced.Instead of redefining all GUI elements within the Designer class itself (thus mixing GUI and implementa-tion functionality), we propose to give it an instance of the IDisplay interface - the exact same interfaceused by all other classes in the game. Only now, the IDisplay interface will be implemented by anotherGUI class, one that accomodates the Designer’s GUI needs instead of the needs of the Tyrant game.Thus the beauty of the new design is demonstrated: new GUI classes can be plugged in at will, withoutnecessitating major changes or even broken classes in the old code.

Therefore we regard the ”fixing” of the Designer class as an opportunity, not as an obstruction. Wemay need to postpone some of the originally planned work. However by demonstrating how the newdesign works, and more importantly, its strength and advantages, we more or less ”prove” that we havedone a good job of separating GUI and implementation functionality (which was in fact our goal).

5.16.2 Detailed description of Designer control flow - 2 phases

1. Phase 1: Designer startupAs previously mentioned, the Designer is started when the user presses ’e’ in the option screen atapplication startup. The class responsible for this screen is GameInitializer, a new class introducedby us to contain part of QuestApp’s functionality. Thus in the refactored application, GameIni-tizalizer will pass an appropriate implementation of the IDisplay interface to Designer, so that itdoes not need to redefine GUI handlers and screens anymore. This implementation of the IDisplayinterface, a class we’ll call DesignerGUIPanel, will be discussed below.

2. Phase 2: Designer control flowThis phase a bit more complicated. Essentially, what happens at designer startup is the following:

(a) A graphical ”LookAndFeel” is set

(b) The Designer’s constructor is called. This method does nothing more than initializing actionsand handlers. The latter will be put in the DesignerGUIClass.

(c) The method go() is invoked. It does the following:

i. Invokes createMapUpdaters(), which creates 3 objects that implement IMapUpdater: ad-dTile, addThing, and deleteThing. It also sets the current mapAdder object to addTile.

ii. Invokes the static method Game.loadVersionNumber() - no problem for refactoring sinceit’s publicly accessible without added dependencies

iii. Invokes createFrame(). This is tougher to tackle, since this is presumably the one that isbroken. It heavily relied on QuestApp, the class that we refactored. Specifically:A. It sets ”Game.debug” to true.B. It creates a QuestApp instance and sets ”isApplet” to false.C. Creates a final Designer object that is set to this, but that is never used: it is unclear

what its purpose is.D. It creates a Frame and sets various attributes such as color, a window listener, etc:

this should be implemented in DesignerGUIPanel.E. It adds a QuestApp instance to the Frame: this should be the IDisplay interface

- but is this possible? This section will probably need to be entirely reworked inDesignerGUIPanel.

F. adds a menubar to the frame via another method that can be moved in its entirety.G. !!!: It replaces QuestApps run() method with one defined by the Designer itself! It is

not entirely clear yet where this thread is started - but this will need to be isolatedin another class, DesignerInitializer for instance. This run() method in question does(roughly speaking) the following: sets a Hero, sets Game’s designermode to true, addslisteners and gamehandler, creates and displays an empty map on the mappanel itrequested from GameScreen, creates a status bar, and creates and displays a seperateframe called ”TilePalette” as well as one called ”ThingPalette”. This is pure GUIstuff.

27

Page 29: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

H. The following line calls QuestApp.init(this), which previously indirectly started theGame thread with the Designer’s thread instead of the Game’s thread. Currentlyhowever this thread is started in TController’s init() method, which is probably thecause for the Designer failing to start.

iv. Calls requestFocus() on the newly created frame.

(d) If necessary the embedded field is set to true.

(e) Then it enters a wait period, until it receives an interrupt terminating the designer. Thissignifies a return to the usual game screen.

5.16.3 What do we know so far?

It is clear from the previous discussion that most of the work done by the designer class involves GUI-work. From starting the Designer from the main branch we know that in that main branch, the menubar is broken as well. That at least is not due to our refactorings.Also, the designer frames (there are three: a main frame that looks like the main Tyrant frame, and2 auxiliary frames containing Things and Tiles that can be placed on the map through clicking) arecreated on top of the Game frame, and when those frames are exited, the user returns to the originalGame frame.In conclusion, it looks like the designer is almost purely a GUI, with little more ”implementation”functionality than that which allows the Designer to save a map. So what does this mean?In practice, it means that nearly all code currently contained in the designer will be moved to thenew DesignerGUIClass and DesignerInitializer, and little (if no code) will remain in the original class.Nonetheless this refactoring is important for two reasons:

1. First of all, it is an exemplary case demonstrating the advantages of the new design by removingthe need to redefine elements in another class.

2. Second of all, this class will be easier to manage in the future, because different types of functionalitywill be cleanly separated and put into classes similar to the ones in the main mikera.tyrant package.

5.17 Reworking the design to accomodate Designer

5.17.1 What’s the problem?

When designing Tyrant’s structure, we did not envision the possibility of multiple frames being used asfront-end, instead of just one. However, that is exactly what the Designer class does. Thus, in order tocleanly integrate Designer into the current model, it needed to be adapted.Moreover, other design problems manifested themselves. What we couldn’t see during initial design(but what dawned on us while ”growing into the project”, was that in fact we had not yet sufficientlyseparated GUI from functionality. When looking for ways to resolve this, an opportunity presented itselfto remove an abstraction layer from the current design.In spite of the fact that restructuring the design was going to take time, and thus would lead to timeshortage for other planned goals, we decided to go ahead with this anyway for the following reasons:

1. If Designer is cleanly integrated into the new structure, it provides a clear example for the developersto look at when tackling other, related future problems.

2. The new design is simpler, more maintainable, and cleaner than the previous one.

3. The other planned goals will be easier to implement with the new design - thus, even if we don’thave the time to do it, someone else easily can.

28

Page 30: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

5.17.2 So what’s the new design?

It’s really very simple. There are 3 potential startup entry classes that can be used to start up Tyrant- two for the normal game (Application mode and Applet mode), and one for the designer mode, thatredefines a lot of the original GUI and adds 2 other frames.Since even though Designer can be started up via the Game, it should also be executable in a standaloneversion, all entry points can be regarded as independent start entries.Therefore three independent classes were created, each performing similar code. The similar code consistsof basically two actions: Instantiating the GUI, and kick-starting the main thread. For each mode,another GUI is used, and for the designer, another main thread is used. However, all three classesinstantiate a common controller class (aptly named GameController). That Controller object receives3 parameters: a GUI object (all GUIs implement a common interface called IDisplay), a thread object(all thread objects implement Runnable), and the command line parameters each class received. Thus,even though the starting point is different, the control flow has been simplified and brought together.This greatly improves readability and maintainability, and through the use of the IDisplay and Runnableinterfaces, the actual GUI and Engine implementations have been decoupled.

5.18 Implementing the new design

5.18.1 Splitting up Designer - Overview

We’ll work in several steps:

1. First of all, a new class StartDesigner will be created, responsible for creating the three main Framesand possibly the underlying implementation classes that are yet to be introduced and named (thiswill be done while refactoring).

2. A DesignerController will be created to handle the creation of the MainDesignerPanel, TilesDesign-erPanel, and ThingDesignerPanel that will be ”pasted” onto the three aformentioned frames. (Thisis analogous to the procedure followed by ApplicationController in the package mikera.tyrant). Itwill also instantiate and run the Runnable class called DesignerInitializer.

3. The method call

Designer.main(new String[]{"embedded"})

will be replaced by a call of the type

StartDesigner.main(new String[]{"embedded"})

5.18.2 Actual implementation

Effectively performing this refactoring proved too much to handle at this point, since time was runningout. What could be done however in the short time span at our disposal, was refactoring the initializationof Designer so that it is initialized similar to StartApplet and StartApplication. Considering the size ofDesigner (it is at least as large, if not larger than QuestApp), refactoring this class in its entirety is notfeasible. However, by creating an initialization process like the one for StartApplet and StartApplication,we have at least paved the way for further refactoring.

5.18.3 Is Designer fixed?

Yes!This was of course not due to the above refactorings, but rather due to adapting those blocks of codethat manipulated QuestApp to the new design (IDisplay, that is). Rewriting no more than 10 lines ofcode so that it uses the new interface instead of QuestApp fixed things.

29

Page 31: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

5.18.4 Making Sure Things Still Work And All Tests Succeed

1. Running Tyrant (applet + application: OK

2. Running the tests: OK

Phew :)

5.19 Making Sure Things Work And All Tests Succeed

Tyrant start-up: OKTests:

• using JUnit: OK

• using Ant: failures!

– mikera.tyrant.test.Thing TC

– mikera.tyrant.test.WorldMap TC

The problem is here that StartApplication TC creates a frame/thread wich is not closed. So alltests that follow use the wrong thread. This problem will be solved later on

5.20 Moving QuestApp.gameOver() and QuestApp.getDeathString() to Game

5.20.1 Justification

As QuestApp was a large class with mixed responsibilities, the goal is to phase out QuestApp, and todivide its responsibilities between classes with clear goals. Game will contain all engine-related methods.Since only one line of gameOver is GUI, this method will be moved to Game. Afterwards the GUIfunctionality will be extracted to the interface IDisplay. GetDeathString is fully engine, so it should bemoved into Game.

5.20.2 Information and overview of steps taken

1. Affected parts in QuestApp: none

2. Other classes modified in order to support this change: GameScreen

3. Tyrant start-up: OK

4. Tests (using JUnit): all tests passed!

5.21 Moving QuestApp.fileEncoding to Game

5.21.1 Justification

This is the last step in emptying QuestApp. Since fileEncoding has to do with saving and restoring agame, it should be moved to Game where the save/restore functionality is. Since only Game uses thisproperty, it should be set private instead of public and it is not nessessary to make it static. Later onthis property will be set in an apart module with save/restore functionality.

30

Page 32: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

5.21.2 Information and overview of steps taken

1. Affected parts in QuestApp: none

2. Other classes modified in order to support this change: Game (Game.gameOver())

3. Tyrant start-up: OK

4. Tests (using JUnit): all tests passed!

5.22 Deleting QuestApp and QuestApplication

5.22.1 Justification

QuestApp was a large class with mixed responsabilities. The goal was to phase out QuestApp and divideits responsibilities between classes with clear goals. This job is done now, so the only thing we still haveto do is to delete QuestApp.

5.23 Information and overview of steps taken

1. Affected classes

• Designer: this class will be refactored by Leen DS, so QuestApp is just commented.

• QuestApplication: this class will be also deleted

• GUIProperties: QuestApp is used here to load images from the .jar using QuestApp.class.getResource(filename). This could be done by every class with the same classpath. So replacing QuestAppby for instance GuiProperties should do the trick too.

• MainGuiPanel: The use of QuestApp in MainGUIPanel is historical and dates back to thebeginnen of the refactorings (it was a property that allowed to bridge the old implementationand the new one so to speak). However, now it isn’t used anymore, and therefore can bedeleted.

• Game: In Game 3 methods are affected:

– scrollTextScreen: just a retrieval of QuestApp, but nothing is done with it, so this linecan be deleted

– getQuestApp: this method was used by the constructor of InputHandler as Component.This will be replaced with (Component) Game.getIDisplayInterface().

– setQuestApp and getQuestApp: this methods are not used anymore, so they can easilybe deleted

2. Tyrant start-up: OK

3. Tests (using JUnit): GameHandler TC.testRunningOverMessagePoint() fails!InputHandler is the culprit: it throws an IllegalArgumentException when creating a KeyEventbased on a component, namely MainGUIPanel. MainGUIPanel is accessed by invoking Game.getDisplayInterface().However, in a testing environment this interface is never set, thus causing an IllegalArgumentEx-ception indirectly originating from Game.getDisplayInterface() returning null. Modifying the testsso that this interface is set correctly solves the problem: all tests now pass. Note that the creationand use of this interface in the tests does not affect the test’s semantics; we did not alter the test’sbehavior in any way nor have we influenced its result.

31

Page 33: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

6 Overview of difficulties encountered

6.1 GUI Testing

Considering Tyrant has little or not tests that exercise GUI functionality, we originally planned to writesome ourselves, so as to cover at least a few basic scenarios. However we encountered quite a few problemsalong the way.First of all, choosing a testing package took some time. Eventually we decided to use Jemmy, a NetBeansproject. After learning how Jemmy worked, we started writing several startup scenario tests that, throughinput towards the GUI, were supposed to exercise the parts of the program that we had refactored.However, first of all it appears that Tyrant is not thread-safe. Executing a batch of ”normal”, non-GUItests along with GUI tests made unrelated tests fail, because the underlying static library that was usedby every test (including ours) was reused and accessed by different GUI threads. In short, running severalGUI tests at once was impossible.This is suspected to be due to JUnit’s internal structure (not closing the threads in time) and the classLib not being thread-safe. Moreover, if we bypassed this problem by using Thread.sleep()’s in the GUItests, the JUnit tests ran, but not from within Ant. Why this is such a problem for Ant is as of yet still amystery, but attempting to write and execute these tests without problems was so time-consuming andfruitless, that in the end we decided to stop worrying about them and instead used thorough walkthroughsof the Game itself by way of GUI-test. The walkthrough is analogous for Applet and Application, andconsisted of starting the Game, creating a Hero, walking around on the map, requesting an InfoScreen,and triggering the message panel. That way a lot of ground is covered: all immediately visible GUIelements have been exercised (classes GameScreen, MessagePanel, InfoScreen, keyhandler/keyadapter)as well as the startup code. Although this is an informal test, it was seen as sufficing for our needs.Unfortunately we were wrong.

6.2 On the accidental breaking of Designer

What we did not know was that Tyrant provided yet a third GUI interface, one that is very rarelyused. However, this interface reused a big chunk of the old interface, and therefore was broken when werefactored QuestApp. However, this Designer class had very little tests, and none that exercised its GUIaspects. Moreover this class resided in a different package and had thus not been thoroughly researchedin the beginning of our reengineering task.All these reasons explain how Designer could break without us noticing right away. It was pure luck thatwe decided to to run a ”full” informal test of the GUI (just in case something was wrong...ahem), andthus stumbled upon the Designer and discovered it was broken.As it happens, we were able to fix this problem - but the lesson has been learned. We should have beenmore thorough in investigating QuestApp’s inverse dependencies, and maybe we should have expectedsomething like this to happen in the first place. In retrospect, the low test coverage overall should haveweighed in more.

6.3 On design changes

We lost quite some time first composing a new design (which is normal), however afterwards we reworkedthat new design (while it was already implemented!) to accomodate the aforementioned Designer class,as well as improving other aspects (less cluttering). On the one hand it can be argued that this is good(a good design means a good foundation for future code), on the other hand it cost us some time, whichwas something we already had a short supply of.We decided to give the go-ahead to the second design, for several reasons (most of which can be foundin the Chronological Overview). The most important one is that this new design is by far our mostcontribution to Tyrant, since reworking the code to use the opportunities offered by this new design tothe fullest can only be done in a later phase, and moreover can be done by anyone familiar with the code.However we were in the ideal position of improving the design yet again, having worked and discussedand thought about it for quite some time - and thus we felt this was an opportunity that we could notlet slip through our fingers.

32

Page 34: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

6.4 On the work that has not been done

Mike requested three classes be refactored: Game, GameScreen, and QuestApp. Of these three, hecalled QuestApp the most important one. Therefore that’s the one we started with. To our satisfaction,QuestApp has been completely split into GUI and non-GUI parts, with the exception of GameInitializer,which we felt was redundant to split up. Whereas previous nearly every class depended on QuestApp,this dependency has been distributed over new classes, and more importantly, has mainly been replacedby dependence on an interface called IDisplay.However, GameScreen and Game have not been refactored. Although we hope that they will be refac-tored by Tyrant developers, we did not have enough time to do the work ourselves.

33

Page 35: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

Part III

Appendices

34

Page 36: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

A Code review

Criteria:----------coding conventions adhered to?clear variable/method naming?in-code comments: good amount? correct?

Classes reviewed:------------------Animal:Good use of in-code commentssee global remarksArmour:Good use of in-code commentsOnly static member variables, only static methodsLots of private static init methodsMember variables declared almost at the bottom of the class instead of atthe top (as is conventional)Being:only static members and methodsstatic members are again declared just before the static method that needsthem, thus their definitions are intersparsed with the method declarationssome TODO’s and handy in-code commentsCombat:same remarks as with previous classesinteresting: public static boolean DEBUG!! may be indicative of debuggingaspects running throughout the class hierarchy?Fire: same as the othersGame:large class, many methodsthus far the only class with some non-static members and methodsseveral do<some_action>() methodscall to some static loadVersionNumber() method?IActionHandler, IMessageHandler, IThingFilter: These are 3 interfaces wheretraditional naming conventions are followed (interface class names startwith "I")Interface: handles waiting for input from the keyboardLib.java:file size is about the same as Game.javacontains non-static members and methodsuseful comments at top of class declarationRPG.java:huge amount of public static properties; presumably this is where standardvalues are keptlots of small utility methods like "max()", "often()", etc.Stuff.java:seems like a very important class!every method has an accompanying comment block

Global findingssigns of procedural coding in Animal.java:instead of using a constructor the class contains a static "init" methodthe class has no members and has only static methodsGod-class? Lib.java: definitely take a look at this

35

Page 37: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

Combat: public static boolean DEBUG: may be interestingGame.java: fairly large file, large number of methods -> worth looking atTODO tasks are used: handy for pinpointing unfinished/unstable code!Almost every class contains a static init() method that each time doesroughly the same thing:Thing t = Lib.extend("base desc", "base item");[t.set(property, value);] ---> several timesLib.add(t);[optionally another call to private static init...() methods declared inthe class]Error is thrown: a customized exception?QuestApp is the applet version of QuestApplication, which is the mainstarter classStuff.java is a map containing lots of properties; this is a data storageclass (important!)

ConclusionLib.java, Stuff.java, Game.java and RPG.java are definitely classes worthlooking atIn general in-code comments are well usedCoding conventions not adhered to consistentlyVariable naming confusing: Thing, Stuff, BaseObject, etc.Gathered some interesting question marks (see global findings)

36

Page 38: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

B Documentation verification

First steps (rough):====================Javadoc (gegenereerd op 23 maart)Er is bijna nooit bijkomende informatie gegeven in Javadoc-stijl.Documentatie op de sourceforge websiteInhoudelijk- sterkte van een item (bv. een item vervloeken verminderd de sterktemet 2- mentale staat van een wezen (vijandig, neutraal, ...)Howto’sHoe een wezen toevoegenminifaq:hoe tyrant uit cvs halen, installeren en uitvoerenDe missie: soort van requirements (natuurlijke taal)Nergens is een datum van laatste aanpassing gegeven.Thing en Creating ThingsDe klasse Thing is duidelijk erg belangrijk. Op de eerste link wordt nietenkel uitleg gegeven over de klasse zelf (wat houdt ze in), maar ook overregels waaraan de developers zich moeten houden, een kleine argumentatie vanwaarom de klasse is zoals ze is.De tweede link legt uit hoe je met een instantie van de klasse Thing kanwerken.Nergens is een datum van laatste aanpassing gegeven.Game balance, Game library en Level:Geven inhoudelijke uitleg en regels waaraan de programmeurs zich moetenhouden.Nergens is een datum van laatste aanpassing gegeven.

Second step (detailed):========================Item quality:found references to item quality in 3 files: Item.java, Lib.java,RangedWeapon

Item.java: 2 methods that contain references to quality:public static String getAdjectives(Thing t)public int getEffectiveQuality()both are commented out: does this mean that the difference between intrinsicand effective quality is not implemented, or that it is implementedsomewhere else?

Lib.java: 2 variable references:public static final String[] qualitystringspublic static final int[] qualityvaluesthe values in the qualityvalues array do not correspond to the ones in thedocs. It is not clear yet whether this is important or not

RangedWeapondon’t think this is the type of quality mentioned in the docsin BaseObject.java there’s a method incStat; it’s not clear whether this iswhat’s meantin the docs (no comments, the code doesn’t say anything either)

37

Page 39: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

can’t confirm these statements:"Needless to say, higher quality items perform better, are more valuable, aremore resistant to damage and are progressively harder to find. Brilliant, Divineand Perfect items in particular should be a rare and exciting find for *any*character at *any* level.""Item damage may reduce the quality, this should probably depend on the itemtype though. Armour quality is likely to deteriorate with damage, but is lesslike to be destroyed. Weapons on the other hand are more likely to retain theirquality (a well-made sword will always be good) but will need repair if they aredamaged or else they will be ruined beyond repair.""The intrinsic quality of an item *can* change, but should only increase veryrarely (e.g. completion of a quest) since we want to make the higher items rareand highly prized. Reduction in item quality may be more common, e.g. corrosion,nasty scrolls, traps."Finding out whether or not this is true would mean reading and understanding toomuch code, and does not seem important enough in the light of our task.It seems to refer more to game play than actual important implementation code.

Tyrant AI that determines whether or not a creature is hostile:There are not 5 but 3 states in the AI class: AI.STATE_HOSTILE,AI.STATE_INHABITANT, AI.STATE_FOLLOWERThis means that AI.STATE_CHAOTIC and AI.STATE_NEUTRAL are either notimplemented or implemented somewhere else (outside of the AI class)Thing contains a method "isHostile" that simply returns the value obtainedfrom the call AI.isHostile()from code read in AI.java (method "isHostile" it seems that even more modes("isInsane", etc) exist? further searches on this string didn’t result inany matches though...

Adding creatures: accurate (see code review)

minifaq: verified by LVGlooks correct (not going over all statements, too much time-consuming and theguide seems up to date)

Creating things:lib.extend: oklib.create: oknew Thing(String name): oknew Thing(Thing old): ok

Game balance: refers to game play, not implementation

Game library: seems correct

Game level:correct, and additionally when a thing is created without specifyinga level, it gets the level the hero is currently at (see Lib.java ->create...())

Other docs found on the Wiki=============================scripts, flags, damage types, modifiers, properties, etc. (many, many more:tyrant definitely has lots of docs)not going to verify all of them, general impression is that design descriptions(which is the only thing really important right now) is correctly documented.Explanations referring to game play are less important.

38

Page 40: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

Very interesting is Mike’s answer to the question why everything is a Thingwithout the use of inheritance (see end of page "Thing")

39

Page 41: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

C Installation guide

Retrieving the source codeOpen EclipseChoose "File" - "Import..."Choose "Checkout Imports from CVS" and press "Next>"Choose "Create a new repository location" and press "Next>"Enter following information and press "Next>"Host = "cvs.sourceforge.net"Repository path = "/cvsroot/tyrant"User = "anonymous"Connection type = "pserver"Enter "tyrant" as the module name and press "Next>"Check "Check out as a project in the workspace" and enter a project name(e.g."Tyrant"), then press Finish

source: Homepage Tyrant

Compiling with AntMake sure eclipse uses a JDKCreate a folder "builds" in the root folder of your projectRight-click on the file "build.xml" and select "Run" - "Ant build...". Thiswill open a dialog boxChoose the tab "Classpath", select the library "Ant Home", then click "AddExternal JARs..." on the right hand sideSelect junit.jar in the plugin folder of EclipseClick Run

source: mini-faq

Compiling with Eclipse’s internal compilerRight-click on the project name, then choose "Properties..." (this will openup a dialog box)Select "java build path"Select the "libraries" tab then choose "Add JARs..."Add forms-1.0.5.jar and junit.jarClick OKPress CTRL+B to build the project

Executing TyrantRight-click on the project name, then click "Run" > "Run...". A dialog boxwill pop upEnter "mikera.tyrant.QuestApplication" as the main classSelect the "Classpath" tab and select "User Entries"Click "Add JARs...", select tyrant.jar in the project root folderSelect the "Source" tab, click "Add..."Select "Archive", press "OK"Select "tyrant-src-****" from the directory Projecthomefolder/bin/builds/,press "OK"ApplyRun > the application will start

source: mini-faq

40

Page 42: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

D Refactor to understand

* QuestApp is an applet, that is not only used as standalone applet but also as acomponent in QuestApplication to do all of its GUI-work (it is added to the outsideapplication frame) -> this is good b/c it means that if we ever need to changeanything GUI-related, we just edit the QuestApp class. The main application does notneed to be changed separately. Questapps keyadapter is passed on to QuestApplication

* Questapps "getInstance" method is used to access the GUI from the Game class in quitea lot of methods -> if we hide this one we can easily detect hot spots where GUI codehas been placed inside the Game class

* I’m marking all hot spots using the syntax "//@lds HOT SPOT"

* Indirectly this getInstance() method is used much more frequently through game’sgetQuestapp’s method; all have been marked using "//@lds HOT SPOT "

* 2 basic uses of getInstance/getQuestapp: init and modification of GUI; possible solutioninvolves adding an extra layer that queues certain events, and an event listener thatsubsequently changes the GUI accordingly. Question: will this solve the dependencies problem?It does mean that they are centralized, and explicitated, which is an advantage, but it maynot be enough of an advantage to justify the extra overhead

* Reading up on design patterns: apparently the solution I presented previously is similarto a well-known GUI design pattern called MVC (Model-View-Controller). It boils downseparating 3 main concerns:o Model: This would be the Game itself with all the creatures, spells, etco View: This would then be the actual GUI (GameScreen and QuestApp)o Controller: regulates command traffic between the Model and the View; the Model andView don’t know of each other. Possibly the commands that need to be passed throughthe controller can be shaped using the Command pattern.

o Links: Bigger picture using MVC , MVC itself , Command pattern , Adapter pattern

* Figured out what the strange getInstance()/setInstance() of the mainly static QuestApp classare for: this is an implementation of the Singleton pattern. The Singleton pattern ensures thatthere is always exactly one instance of a specific class present. This article from JavaWorldexplains. It appears that this pattern has not been implemented in a thread-safe way. Howeverthis isn’t a problem since Game, Scroll, Recipe and Wand (the only classes that callgetInstance()) are not threads.

* Rough blueprint of the average static class in Tyrant:o Set of class-specific properties, hardcodedo Methods to create specific Things (factory methods) usually named "create..."o init() method that creates one or more initialization objects and add them to Libo possibly a "getInstance()" method as well

41

Page 43: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

E Clover coverage guide

Making Clover work with Tyrant is not that simple. Here is a litle how-to.Right-click on the project root folder and select "Properties"Select the "Source" tab from "Java Build Path"Remove the project root folderClick on "Add Folder...". A dialog box appears.Select "Create New Folder...". Another dialog box appears.Enter a folder name, e.g. srcPress "OK" twice (both dialog boxes are closed now)Make sure "Allow output folders for source folders" is checkedPress "OK"; the "Properties" dialog box is closed.Move all folders into the /src folderRight-click on the project root folder and select "Properties"Select the "Libraries" tab from "Java Build Path"Remove "forms-1.0.5.jar", "junit.jar" and "looks-1.2.2.jar"Press "Add JARs...", A dialog box appears.Add "forms-1.0.5.jar", "junit.jar" and "looks-1.2.2.jar" from the directory"src\builds\lib\"Press "OK twice"Press CTRL+B to build your project, this should work.Download the Eclipse plugin from the cloverwebsite. You will need an accountin order to download the plugin and a 30-day trial licenceFollow the installation steps in the manualBuild the project (Compile with Clover must be activated)Run the JUnit testsClick on "Refresh Coverage Data" on the "Clover View"

42

Page 44: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

F Metrics results for Tyrant

LOC (Lines Of Code) Class Top 3--------------------------------Monster (1945)Spel (1089)Map (1086)

LOC (Lines Of Code) Method Top 3----------------------------------Equipment.initContainers (417)Spell.initOffensiveSpells (304)Hero.applyProfession (290)

Number of Methods Top 3------------------------Thing (144)Map (128)GameScreen (53)

Number of Static Methods Top 3--------------------------------RPG (53)Spell (50)Game (47)

Number of Attributes Top 3---------------------------Designer (32)ThingEditor (15)MapMaker (3)

Number of Static Attributes Top 3----------------------------------RPG (154)Clothing (76)Tile (65)

Depth of Inheritance Hierarchy--------------------------------Shared first place (6) byInventoryPanelListScreenInfoScreenDetailedListScreenCharacterScreenTitleScreenGameScreenArtScreen

43

Page 45: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

InventoryScreen

44

Page 46: Tyrant: On a quest to find The Perfect Designlore.ua.ac.be/Teaching/SRe2LIC/reports/deSchutter_vanGompel.pdf · Tyrant: On a quest to find The Perfect Design Leen De Schutter &

References

[1] Homepage tyrant. http://tyrant.sourceforge.net.

[2] Tyrant coverage report.http://vanhoof.dyndns.org/∼sre/03-22/mikra/tyrant/pkg-summary.html.

[3] Tyrant faq. http://tyrant.sourceforge.net/game/faq.php.

[4] Tyrant forum. http://sourceforge.net/forum/forum.php?forum id=52946.

[5] Serge Demeyer, Stephane Ducasse, and Oscar Nierstrasz. Object-Oriented Reengineering Patterns.Morgan Kaufmann Publishers, 2003.

45