Chapter 4 Game Analysis Guidelines
This is a list of general guidelines to analyze a videogame or a specific segment of it. It can also be helpful to compare different games. This is not a template, but rather a list of aspects that can be part of your analysis. Every section relates to all the others; they are listed in an order that would facilitate building up the argument, but can be rearranged depending on the goal of your analysis. Which sections you should expand on depends on the focus of your discussion. Whether you’re analyzing a whole game and its context, or your experience of playing the game, or one specific game mechanic, are all factors that will shape your analysis.
A game analysis should be a critique, rather than a review. A critique breaks down the object of study, using theoretical concepts systematically to structure and support it. Critiques are based on argumentation and supported by evidence. Reviews evaluate the quality of the game reviewed, and even though they also have to be supported by argumentation, they also tend to be more subjective. The goal of a game analysis is not to establish how good or bad a game is, but highlight and rationalize the aspects that make the game worth studying and contribute to understanding videogames better. Thus, you will be expected to write a critique, which is very different from a review for a blog.
4.1 Before you start writing: play the game
- Play the game extensively
Try to complete the game, or at least familiarize yourself with all the modes. You should master the game as much as you can. If you’ve played it before, play it again, to have the game fresh in your mind—replay always helps achieving new insights about the game. The time you should be playing the game varies from game to game. In your analysis, be explicit about how you played the game, if there are different options (e.g. level of difficulty, character that you used.)
- Take notes while you play.
It’s good to keep track of things you come across in the game. What do you like?
What don’t you like? Is there something that you did not expect? Does a specific part of the game remind you of something else (another game, or a movie, or some other event)? Taking notes of potential examples for your paper is also a good habit. If you can have a corresponding savegame for reference, even better.
- Use walkthroughs wisely.
Anyone can get stuck and might need help, especially if it prevents them from getting far enough in the game (and therefore from playing it extensively). Cheat codes, watching walkthrough videos, or having a friend play for you are ways to help you know more about the game, but they should not be the predominant mode in which you experience it. If you just follow a walkthrough from the start,
you will be reproducing someone else’s experience of the game, not your own. It would be like reading Cliff’s Notes instead of the actual book; in the end your paper will show that your knowledge of the game is shallow. Using a walkthrough can also circumvent some of the problems the game may have, since people who write walkthroughs often instruct players how to avoid bugs and broken design. You should also have first-hand experience of the problems that come across while playing the game. Use walkthroughs as a way to learn more about the game, but do not let the walkthrough dictate your experience. If you use walkthroughs or cheats, be explicit about it, and note how that may have changed your experience and knowledge of the game.
- Read what other people have written about the game In order to know more about your game, read about it: reviews, academic articles, press releases, newspaper articles, developer diaries, postmortems, etc. It will help you get an idea of what other people think about the game, its production history, and know more about the context of the game. The game box and manuals can also be sources of relevant information; if your copy of the game does not have its original box, some websites have repositories of them.
4.2 Types of Analysis
Once you’ve played the game, you should decide what you want to focus on. What makes this game worth analyzing? What are you going to discuss?
The analysis is an essay, so the main argument of your analysis should be expressed as a thesis statement. For example: “The morality system in this game is usually hailed as unique and complex; however, I will prove that, in closer examination, it is merely a binary system, and does not allow for complex moral choices.” Another example: “Although Ernest Adams argues that the design of Metal Gear Solid 2 is faulty because it breaks the”fourth wall“, my argument is that it does not break the immersion in the game because it actually extends the space of the game into the player’s space.”
You probably cannot write something very long, so choose early on what aspects of the game you want to highlight. Rather than covering a lot of aspects, select one or two important ones, and analyze them in depth, using other sections of the analysis as support for your main point. Are you going to talk about the whole game, or just a few sections? Are you going to base it on your own experience or other people’s? Are you going to discuss other people’s takes on the game? Are you going to discuss how the game exemplifies a particular cultural stance or trend? A brief summary of what the discussion is about should constitute the thesis statement of your analysis, and it should appear in the introduction to your game. Be clear and concise about what you will be analyzing and how. Throughout the paper, you must apply the readings covered in class. The theory will provide you with conceptual tools to analyze the game, by applying them, or by proving that the game you are dealing with is an exception to them, or by finding an area that the theory does not account for that is exemplified in your game. Try to start with the theory and how it is relevant to the game, rather than writing an argument and then pasting in the relevant vocabulary or references. Different types of textual analysis will elaborate more on certain sections. Some analyses can expand and compare more than one aspect; however, beware of wanting to cover too much and losing focus. The following are just a few examples of different types of textual analysis depending on their focus, but you can come up with your own depending on what you want to highlight from the game. Browse the analyses in the different issues of Game Studies http://gamestudies.org/ to find a range of different academic analyses of games.1 - Comparative Analysis:
A game can be compared to other media, which entails elaborating on that section of the analysis. This means it is a double analysis, of both the game and the other media it is being compared to. This approach makes it necessary to define very well what the terms of the comparison are, so you can build a strong argument.
Two games can be compared to each other, which will require expanding on the specific aspects being compared (e.g. aesthetics or different elements in the formal analysis). Talking about two games in the same paper does not constitute a comparative analysis: you must select the aspects that allow the comparison.
For example, comparing World of Warcraft with Passage is a big leap, since they seem to have so little in common. Choose a set of aspects that allows the comparison, either by highlighting the contrast, or by drawing similarities where there may appear to be none.
For instance, A comparative analysis can deal with how different games implement a similar fictional universe or premise (Cooking Mama vs Diner Dash; Star Wars: Knights of the Old Republic vs Star Wars: Shadows of the Empire). It is important to be selective about the number of aspects being compared, since it is very easy to make it into a double analysis instead of a comparison.
- Reading / Interpretation
This type of analysis understands what happens in the game as a representation of something else, e.g. Tetris as a metaphor for American consumerism, as Janet Murray argued. It can also be a symbolic reading encouraged by the game itself,
such as a psychological reading of Silent Hill 2 (see Rusch in Well Played 1.0).
Interpretations usually draw heavily on pre-existing interpretive theories and analytical methods, from Freudian / Jungian approaches to deconstruction.
Interpretations are subjective, so the key to provide a convincing reading is to ground it on evidence, and follow a systematic approach based on pre-existing methods. You can also analyze the game from the standpoint of the theories and methods of a field, from education to sociology or film studies. Game studies is an interdisciplinary field, as you will see through the course. That does not mean that other disciplines cannot analyze games and see how they relate to their knowledge domain.
- Historical analysis:
A historical analysis can deal, for example, with the production history of a game,
its reception, or how it marked a specific milestone in videogame history.
Historical analyses expand on the socio-historical context of the game, although depending on what historical aspect is highlighted, other sections may also have to be strengthened. The historical innovations of a game, for example, will probably require expanding on its formal qualities. For example, Drew Davison, in his analysis in Well Played 1.0 accounts for his experience as a player of World of Goo throughout his development. Given that he was part of the process, he can provide a unique perspective in his analysis. - Case study The “case study” format can be used as a way to prove or disprove a set of theoretical assumptions, by applying them in depth to a particular game. The game becomes the reference example of a specific theory or approach. For instance, the analysis of Ultima Underworld in Well Played 1.0 explores specific methods to integrate gameplay and storytelling. Metal Gear Solid 2 could be approached as a case study of using self-reference and breaking the “fourth wall” as a way of making a commentary on the player’s behaviour. These are just a few examples of what you may do. For the purposes of this class, try to marry the game to the theory, and try to get an idea of what you want to say early on.
4.3 Contextualize
Providing a frame of reference to understand your analysis is basic to make it accessible to your reader, particularly if they are not familiar with the game. Games, like any other media artifacts, are not produced in a void. They are the product of their times: the technology available at the moment, the people who made them, or other socio-cultural trends that marked that historical moment. Unless your analysis focuses specifically on any of these areas (e.g. a historical analysis or a comparison with other media), this should be a brief section. Your contextualization should include at least three of these aspects. - Game Developer/Studio; Artists, Designers, Programmers, Writers (or whoever is relevant). The creative team of a game is important, particularly in the case where it connects to other relevant games they worked on before or after this game. Some development studios or designers have certain trademarks, or specialize on a particular genre (e.g. Valve, Square Enix or Will Wright), so choosing one of their games can mark a specific time in the evolution of their games, which might be important to the context of your analysis. - Game genre and related games A game genre usually has a set of established conventions behind it, such as First Person Shooters, adventure games, real-time strategy games or Role-Playing Games. Establishing the game within a genre can help determine, for instance, why it is like other games of the same type, or what is innovative about it. If the game partakes of conventions from different genres, that can also be a way of explaining what sets it apart from other games. The relations to other game genres do not have to be exclusively digital–board and card games, as well as other traditional games, can be the origins of certain mechanics. The game of your choice can relate to other games by any of the elements listed on these guidelines: developer, genre, publisher, by being released at the same time, similar mechanics, similar themes (e.g. space flight simulators), being part of a franchise (e.g. Star Wars or Mario), using similar controls, games that it has influenced or that later tried to copy it, etc. - Technological context What platform(s) was the game developed for? It is important to note which version of the game you are playing, e.g. Assassin’s Creed for the DS is a different game from the PS3 version. The technology will determine, for instance, the length of the game or whether there is an ending (think of arcade games in the early 80s, where games typically did not end). In other cases, such as the Virtual Boy or the Wii, the technology can also shape the way the way the game is played. - Socio-historical context When was the game made? It’s different to analyze a game from the 70s, when videogames were almost exclusively played in arcades, than a recent game, now that videogames start to enjoy a more general acceptance in cultural terms. Where was it released? A game released in the US in the mid 80s (after the socalled videogame crash) may not have had the same influence as a game released in Europe (where videogames were played almost exclusively in home computers). - Audience Who is the game intended for? Games can create communities around them, at times fostered by the developers themselves. The fans of a particular game can be a phenomenon worth studying in itself, for example, as creators of content for it, as critics/reviewers, or as assistants to other players by providing walkthroughs or strategy guides. Studying the game community in depth can veer away from the textual analysis provided here, so be aware that if you study the community you may not be analyzing the game itself. It is a perfectly valid analysis, but different from the one proposed here. - Relations to other media Is the game part of a franchise, a tie-in with a work in another medium (e.g. Lost - Via Domus, Wall-E)? Is it an adaptation of the work in another medium, or is it only using the same characters and settings? Is it inspired by a situation or premise of another medium (e.g. Dead Rising taking the zombies-in-a-mall premise of Dawn of the Dead)? Does it incorporate and re-work pre-existing stories (e.g. Too Human using Norse mythology as the basis for its story, but transposing it to a high-tech world) Does it use other works as documentation, background or inspiration (e.g. the writings of Ann Rynd as background reference to Bioshock)? - If you are analyzing a game segment, how does the segment relate to the rest of the game? What happened before and after? Are the mechanics different in this part of the game? Does this section need a different strategy from the rest of the


| Package | com.pblabs.animation |
| Class | public class Animator |
| Inheritance | Animator flash.events.EventDispatcher |
| Subclasses | PointAnimator |
To animate more complex types, subclass this and implement the interpolate method.
| Property | Defined by | ||
|---|---|---|---|
| animationType : AnimatorType
The type of playback to use for the animation.
| Animator | ||
| currentValue : * [read-only]
The current value of the animation.
| Animator | ||
| duration : Number
The time it should take to animate from the start value to the target value.
| Animator | ||
| elapsed : Number [read-only]
The amount of time that has passed since the animation started.
| Animator | ||
| isAnimating : Boolean [read-only]
Whether or not the animation is currently playing.
| Animator | ||
| repeatCount : int
The remaining number of times the animation will be repeated.
| Animator | ||
| startValue : *
The value the animation should start at.
| Animator | ||
| targetValue : *
The value to animate to.
| Animator | ||
| totalRepeatCount : int [read-only]
The total number of times to repeat the animation.
| Animator |
| Method | Defined by | ||
|---|---|---|---|
|
animate(elapsed:Number):void
This should be called every frame to perform the animation.
| Animator | ||
|
finish():void
Set the current value to the target value instantly.
| Animator | ||
|
play():void
Starts playback.
| Animator | ||
|
reset():void
Resets the animation to all the values it had before it started playing.
| Animator | ||
|
reverse():void
Reverses the animation, effectively setting the start value to the target value and
the target value to the start value.
| Animator | ||
|
start(startValue:*, targetValue:Number, duration:AnimatorType, type:int, repeatCount:* = 0):void
Starts the animation.
| Animator | ||
|
stop():void
Stops playback.
| Animator |
| Method | Defined by | ||
|---|---|---|---|
|
interpolate(start:*, end:Number, time:*):*
Performs the actual animation.
| Animator |
| Event | Summary | Defined by | ||
|---|---|---|---|---|
| This event is dispatched by an Animator when the animation completely finishes. | Animator | |||
| This event is dispatched by an Animator when the animation has finished one iteration and is repeating. | Animator | |||
| This event is dispatched by an Animator when the animation is resumed after being manually stopped. | Animator | |||
| This event is dispatched by an Animator when the animation first starts. | Animator | |||
| This event is dispatched by an Animator when the animation is manually stopped. | Animator |
| animationType | property |
animationType:AnimatorType
[read-write]The type of playback to use for the animation.
Implementation public function get animationType():AnimatorType
public function set animationType(value:AnimatorType):void
| currentValue | property |
currentValue:*
[read-only]The current value of the animation.
Implementation public function get currentValue():*
| duration | property |
duration:Number
[read-write]The time it should take to animate from the start value to the target value.
Implementation public function get duration():Number
public function set duration(value:Number):void
| elapsed | property |
elapsed:Number
[read-only]The amount of time that has passed since the animation started.
Implementation public function get elapsed():Number
| isAnimating | property |
isAnimating:Boolean
[read-only]Whether or not the animation is currently playing.
Implementation public function get isAnimating():Boolean
| repeatCount | property |
repeatCount:int
[read-write]The remaining number of times the animation will be repeated.
Implementation public function get repeatCount():int
public function set repeatCount(value:int):void
| startValue | property |
startValue:*
[read-write]The value the animation should start at.
Implementation public function get startValue():*
public function set startValue(value:*):void
| targetValue | property |
targetValue:*
[read-write]The value to animate to.
Implementation public function get targetValue():*
public function set targetValue(value:*):void
| totalRepeatCount | property |
totalRepeatCount:int
[read-only]The total number of times to repeat the animation.
Implementation public function get totalRepeatCount():int
| animate | () | method |
public function animate(elapsed:Number):void
This should be called every frame to perform the animation.
Parameterselapsed:Number — The amount of time that has elapsed since the last call to this.
|
| finish | () | method |
public function finish():void
Set the current value to the target value instantly.
| interpolate | () | method |
protected function interpolate(start:*, end:Number, time:*):*
Performs the actual animation. This can be overridden by subclasses to interpolate more complex types. This default implementation will only work for values that can use the +, -, and operators.
Parametersstart:* — The value to interpolate from.
|
|
end:Number — The value to interpolate to.
|
|
time:* — The interpolation factor. A value of 0 will return the start value, a
value of 1 will return the end value. A value of 0.5 should return the value half
way between start and end.
|
* — The interpolated value.
|
| play | () | method |
public function play():void
Starts playback.
| reset | () | method |
public function reset():void
Resets the animation to all the values it had before it started playing.
| reverse | () | method |
public function reverse():void
Reverses the animation, effectively setting the start value to the target value and the target value to the start value.
| start | () | method |
public function start(startValue:*, targetValue:Number, duration:AnimatorType, type:int, repeatCount:* = 0):void
Starts the animation. This is simply a shorthand way to specify all the necessary parameters for playback. Play can be called instead if the properties are already set.
ParametersstartValue:* |
|
targetValue:Number |
|
duration:AnimatorType |
|
type:int |
|
repeatCount:* (default = 0 )
|
| stop | () | method |
public function stop():void
Stops playback. It can be resumed by calling Play.
| ANIMATION_FINISHED_EVENT | event |
com.pblabs.animation.AnimationEvent
com.pblabs.animation.AnimationEvent.ANIMATION_FINISHED_EVENT
This event is dispatched by an Animator when the animation completely finishes.
| ANIMATION_REPEATED_EVENT | event |
com.pblabs.animation.AnimationEvent
com.pblabs.animation.AnimationEvent.ANIMATION_REPEATED_EVENT
This event is dispatched by an Animator when the animation has finished one iteration and is repeating.
| ANIMATION_RESUMED_EVENT | event |
com.pblabs.animation.AnimationEvent
com.pblabs.animation.AnimationEvent.ANIMATION_RESUMED_EVENT
This event is dispatched by an Animator when the animation is resumed after being manually stopped.
| ANIMATION_STARTED_EVENT | event |
com.pblabs.animation.AnimationEvent
com.pblabs.animation.AnimationEvent.ANIMATION_STARTED_EVENT
This event is dispatched by an Animator when the animation first starts.
| ANIMATION_STOPPED_EVENT | event |
com.pblabs.animation.AnimationEvent
com.pblabs.animation.AnimationEvent.ANIMATION_STOPPED_EVENT
This event is dispatched by an Animator when the animation is manually stopped.


Legacy is everything
and everything is legacy
The art of tackling legacy and survive
Who am I
This talk and all materials are on github.
What is legacy?
- noun
- an amount of money or property left to someone in a will.
- adjective
- denoting or relating to software or hardware that has been superseded but is difficult to replace because of its wide use.
Legacy code
legacy code is code without tests (Michael Feathers)
Michael Feathers' Working Effectively with Legacy Code (ISBN 0-13-117705-2)
Everything is
code that doesn't change is legacy
You have it!
- code inherited from others
- code from dependencies
It's everywhere!
- even in code just written!
Legacy as a value
- The code exists in production
- Generates revenue for the company
Legacy as a constraint
- code is hard to change
- takes time to add features
- even harder to test and validate
- increased costs at every interaction
Not fun to work with
Why we ended up there?
- design choices
- development and business practices
- technical decisions
- technical obsolescence
Get the basics right
- environment
- processes
- planning
Environment
- Source control
- Continuous Integration
- Unit Tests
- Functional Tests
- Defect Tracking
- Documentation
Processes
- New features
- Bug fixes
- Deployments
- Infrastructure
- Refactoring
Change
- Plan the change
- Change the plan
Your best tool
Case study
- Publishing company
- Education company
- SaaS company
Closing notes
- Remember: Mind your code!
References
And many more...


Kindle + Python = e-Ink Dashboard (part 2)
repurposing an old kindle paperwhite 3
In the last post a Kindle Paperwhite 3 was jailbroken, Python 3.8 installed and all boilerplate code was added to have a way to start our script and launch it from KUAL. Now we can actually dive into the actual Python code that will turn the device in a proper DashBoard. To do this we’ll create a miniature ETL pipeline (Extract - Transform - Load), that will fetch data from relevant websites (the extract part), combine it into a dictionary (the transform) and put all parts in an SVG image (the load). The latter can then be converted into a PNG which we can show on the screen.
As always, all code for this project can be found on GitHub.
Coding the Extract Functions
I have plenty of devices telling me the weather forecast, so I didn’t want to do yet another weather dashboard. Matter of fact, a lot of the code here is based on a KUAL Extension that does exactly that. Check it out here if you are looking to do a weather dashboard. I’ll pick something more specific for me, but it should be relatively easy to adapt the code to other websites and API’s to make a dashboard tailored to your own interests.
After some thinking I decided to pull data from three websites; Google Scholar to grab the number of citations my publications have received as well as my H-index, my current Gwent rank and score (a competitive online card-game) and from TVMaze air dates of upcoming episode for series I like. To avoid making the installation of this extension overly complicated, no additional packages will be used, so only the standard library will be used. Unfortunately, this means getting websites with urllib and regular expressions rather than requests and BeautifulSoup. Though, moving some complexity from the installation (jailbreaking a Kindle to get this to run is hard enough already) to the code is an acceptable trade-off.
Grabbing Data from Google Scholar
All functions to grab and parse data from websites is contained in extract.py
, my Google Scholar page is the
easiest to parse so let’s start there.
import ssl
import urllib.request
import re
import json
def get_google_scholar(url):
ssl_context = ssl._create_unverified_context()
with urllib.request.urlopen(url, context=ssl_context) as response:
html = response.read()
hits = re.findall(r'(\d+)	', str(html))
fields = ['citations', 'citations_recent', 'h_index', 'h_index_recent', 'i10_index', 'i10_index_recent']
return dict(zip(fields, hits))
As far as getting and parsing an html page goes, this couldn’t be more straightforward. We use urllib to get the html data and with a single regular expression can get all fields in the table with the citation statistics. This should give us six hits on each page and by using the zip and dict function it is possible to quickly merge those hits with the correct names for the fields.
One odd thing is that the ssl_context needs to be created and added to the request. While the code works fine on my computer without, it gives an error on the Kindle without this bit included.
Getting the Gwent Profile Data
While a few simple lines sufficed for Google Scholar, getting the data from my Gwent profile contains a lot more ugly code. Here the lack of a proper package to parse HTML, like BeautifulSoup, is really tangible. However, while not the prettiest code, it get’s the job done. It is just a matter of finding the spot in the HTML code where the piece of information is located and writing up a regular expression to parse it out.
def get_gwent_data(url):
ssl_context = ssl._create_unverified_context()
with urllib.request.urlopen(url, context=ssl_context) as response:
html = response.read()
output = {
'player': ''.join(re.findall(r'\\n\s+(.*?)', str(html))),
'mmr': ''.join(re.findall(r'
.*?(.*?)
', str(html))).replace(',',''),
'position': ''.join(re.findall(r'
.*?(.*?)
', str(html))).replace(',',''),
'rank': ''.join(re.findall(r'(.*?)', str(html))),
'ladder': ''.join(re.findall(r'
(.*?)
', str(html))),
}
return output
Note that re.findall returns a list of all hits, which should contain one element here. Though rather than extracting that hit with its index, the join function is used. Due to this little trick, if the Gwent website is updated and the regular expression won’t match the code won’t yield errors, it will just have an empty strings as values. If the values no longer appear on the dashboard it is time to revise the code, but the other items will still work as planned.
TVMaze
TVMaze has an API that returns JSON objects, so that is easy to convert to a dict and doesn’t need messing around with regular expressions to parse out parts of HTML. However, here there is an extra complexity that data for each show (which will be passed to the function as a list of IDs) has to be fetched first. If that contains a link to the upcoming episode we need to get that endpoint additionally to find the name of the episode and the airdate.
def get_tvmaze_data(ids):
output = []
ssl_context = ssl._create_unverified_context()
for id in ids:
url = 'http://api.tvmaze.com/shows/%d' % id
with urllib.request.urlopen(url, context=ssl_context) as response:
data = json.load(response)
links = data.get('_links', {})
if 'nextepisode' in links.keys():
with urllib.request.urlopen(links['nextepisode']['href'], context=ssl_context) as episode_response:
episode_data = json.load(episode_response)
output.append(
{
'name': data.get('name', 'error'),
'episode_name': episode_data.get('name', 'error'),
'airdate': episode_data.get('airdate', 'error'),
}
)
return sorted(output, key=lambda x: x['airdate'])
Caching and Catching Errors with a Decorator
Most sites don’t provide new data every hour, so there is no need to fetch the data that often. We can simply store the results and the next time we refresh check the time since the last modification. If that is recent enough we load the data and provide that. If the file is older than the chach time, we fetch new data from the internet.
As the data comes from the internet a few things can go wrong, the Kindle’s Wifi might not come up fast enough, or the internet might be down, or one of the websites times out … and these errors aren’t caught yet. I also don’t want the dashboard to display empty information for an hour or more because one website was temporarily unreachable. Here we can also leverage the cache, if we fail to get up to fetch data from the internet, we load the cache regardless of the age and show that. It is better to show (slightly) out of date information than no information at all.
The decorator shown below elegantly combines both of these, note there is some boiler plate code for checking the last
modification to a file. This part of the code is included in ./dashboard/bin/cache.py
import json
import os
from datetime import datetime
from functools import wraps
cache_dir = '/mnt/base-us/extensions/dashboard/cache/' if os.name != 'nt' else '../cache'
def hours_since_last_modification(file_path):
""""
Returns the number of hours since a file was modified. -1 indicates the file doesn't exists
"""
if os.path.exists(file_path):
last_modification = os.stat(file_path).st_mtime
return (datetime.now().timestamp() - last_modification) / 3600
else:
return -1
def cache(cache_file, cache_time):
"""
Decorator that combine two things:
* if the decorated function fails (for any reason) it will pull the most recent data
from cache and return those.
* if the cache file is more recent than cache_time and return the
cached data if the file is recent enough
:param cache_file: File to write cache to
:param cache_time: How long (in hours) a file should be cached
"""
def deco_cache(f):
@wraps(f)
def f_cache(*args, **kwargs):
hslm = hours_since_last_modification(cache_file)
if 0 <= hslm < cache_time:
with open(cache_file, 'r') as fin:
output = json.load(fin)
return output
try:
output = f(*args, **kwargs)
with open(cache_file, 'w') as fout:
json.dump(output, fout)
except:
with open(cache_file, 'r') as fin:
output = json.load(fin)
return output
return f_cache
return deco_cache
To get this running we’ll need to decorate the extract functions like shown below. The decorator takes one argument, the filename to write to (potentially this could be done automatically based on the decorated function’s name).
# ...
from dashboard.bin.cache import cache_dir, cache
# ...
@cache(join(cache_dir, 'scholar.json'), 8)
def get_google_scholar(url):
# ...
@cache(join(cache_dir, 'gwent.json'), 1)
def get_gwent_data(url):
# ...
Time to Transform and Load
So now we have to finish run.py
which will be called by the shell script every hour, run the extract functions,
combine the output and visualize it. An easy way to create a dashboard is using an SVG file that looks exactly the way
you like, but with a token in spots
where you want the dynamic text and values to appear. I’ve used this trick before and this is also what was used in the
weather dashboard I’ve been using as a reference for this project. This is very easy code-wise, just see the updated
run.py
below. All extracted data is combined in a dict, ./svg/template.svg
is loaded as a text file and all the
tokens are replaced by the values to display. Finally, the output is written to disk. There is one line of code that
will check if the operating system is windows and adjust the path accordingly. This is useful for debugging as I can
execute the code on my main machine without issues.
# bin/python3
# encoding: utf-8
from datetime import datetime
import os
from os.path import join
from extract import get_google_scholar, get_gwent_data, get_tvmaze_data
scholar_url = "http://scholar.google.com/citations?user=4niBmJUAAAAJ&hl=en"
gwent_url = "http://www.playgwent.com/en/profile/sepro"
tvmaze_ids = [6, # The 100
79, # The Goldbergs
38963, # The Mandalorian
17128 # This Is Us
]
svg_path = '/mnt/base-us/extensions/dashboard/svg/' if os.name != 'nt' else '../svg'
def create_svg(svg_data, svg_template, svg_output):
with open(svg_template, 'r') as fin:
template = fin.read()
for k, v in svg_data.items():
template = template.replace(k, v)
with open(svg_output, 'w') as fout:
fout.write(template)
def fmt_date(date_input):
d = datetime.strptime(date_input, '%Y-%m-%d')
return d.strftime('%d/%m/%Y')
def is_today(date_input, fmt="%Y-%m-%d"):
return date_input == datetime.now().strftime(fmt)
if __name__ == "__main__":
# Get Data
gs_data = get_google_scholar(scholar_url)
gwent_data = get_gwent_data(gwent_url)
tvmaze_data = get_tvmaze_data(tvmaze_ids)
# Combine into dict
svg_data = {"GS_HINDEX": gs_data.get("h_index"),
"GS_CITATIONS": gs_data.get("citations"),
"GWENT_LADDER_RANK": gwent_data.get("ladder") + (" (Rank " + gwent_data.get("rank") + ")" if "Pro" not in gwent_data.get("ladder") else ""),
"GWENT_MMR": gwent_data.get("mmr"),
"GWENT_POSITION": gwent_data.get("position"),
"LASTUPDATE": "Last Update: " + datetime.now().strftime("%d/%m/%Y - %H:%M:%S")}
for i in range(3):
if i < len(tvmaze_data):
svg_data["TV_SHOW_%d" % (i + 1)] = tvmaze_data[i]["name"]
svg_data["TV_EPISODE_%d" % (i + 1)] = tvmaze_data[i]["episode_name"]
svg_data["TV_AIRDATE_%d" % (i + 1)] = "TODAY" if is_today(tvmaze_data[i]["airdate"]) \
else fmt_date(tvmaze_data[i]["airdate"])
else:
svg_data["TV_SHOW_%d" % (i+1)] = "No upcoming episodes found"
svg_data["TV_EPISODE_%d" % (i + 1)] = ""
svg_data["TV_AIRDATE_%d" % (i + 1)] = ""
# Load Data into SVG
create_svg(svg_data, join(svg_path, "template.svg"), join(svg_path, "tmp.svg"))
Revising start.sh
Now run.py
will create a new SVG file each time it is called. However we need to revise start.sh
to convert this
SVG into a PNG image and show that image on the screen. The Weather Dashboard uses a combination of rsvg-convert and
pngcruch to create a png file compatible with the Kindle’s own eips
command to put it on the screen. Here, the
SVG is still converted using the same tool, but fbink
is used, which can put any png image on the screen correctly,
without the need for an additional conversion using pngcruch. Furthermore, the temporary files will be cleaned up in this
script.
One big issue is that the Kindle mounts most partitions with the noexec
flag, meaning you can’t execute code and
scripts from there. For scripts that isn’t much of an issue as you can overcome this by starting them through the
interpreter; /bin/sh


NuGridPy¶
NuGridPy is a Python package containing tools to access and analyse (e.g. plot) various output formats (including the hdf5-based se format) from NuGrid codes (mppnp and ppn) and from the MESA stellar evolution code.
Documentation:¶
nugridpy.astronomy |
Constants and methods for astronomy & astrophysics |
nugridpy.ascii_table |
Ascii_table.py: read and write simple ascii tables |
nugridpy.data_plot |
data_plot.py |
nugridpy.grain |
grain.py is a collection of routines to analyze presolar grain data |
nugridpy.h5T |
h5T.py V1.1 created by Will Hillary |
nugridpy.mesa |
MESA output data loading and plotting |
nugridpy.nugridse |
nugridse is a collection of plots of data in se-type h5 files. |
nugridpy.ppn |
Analyze and visualize the output of single-zone ppn simulations |
nugridpy.utils |
utils.py |


H-SMILE backgrounds
mono color background
gradient color background *
repeat image
repeat fixed image
x repeat image
y repeat image
no repeat image
expandable background image *
expandable background image with stretchable sections *
stretchable background image *
| cell #1 | expandable table backgrounds * |
| cell #3 |
* - cases marked by this symbol can be viewed properly only in
engines based on H-SMILE core.