Skip to content

Latest commit

 

History

History
28 lines (18 loc) · 6.47 KB

File metadata and controls

28 lines (18 loc) · 6.47 KB

React Demo Game

The demo game is present to help you test your Adagrams implementation. At its core, the demo game is a bunch of UI that is built around the Adagrams API, which consists of the four methods drawLetters, usesAvailableLetters, scoreWord, and highestScoreFrom.

As a result, when you first start the demo game, before you've implemented any of the Waves, the demo game won't function correctly! Specifically, it starts off thinking that every hand of letters is ["H", "E", "L", "L", "O", "W", "O", "R", "L", "D"], that any word at all "uses" those letters, and that everything is worth 0 points. As you implement the Adagrams functions (and pass its tests), you make it so the demo game functions correctly.

Adagrams Proxy—The Proxy Pattern

The way the demo game functions without your implementation is by applying the Proxy Pattern to the methods of your adagrams.js. The Proxy in this case is an object defined in adagrams-proxy.js. This proxy object implements the same "interface" as your real Adagrams—that is, it defines the same four functions, with the same names, parameters, and return types—and provides default behavior for any cases where the real Adagrams returns undefined—that's the default return value for any JavaScript function. When you start implementing your Adagrams, and the functions stop returning undefined, the Proxy automatically switches to using your implementation for the function instead of its default behavior.

The traditional definition of the Proxy Pattern explains that two "concrete" classes will inherit from an "interface". In other languages besides JavaScript, an interface is a way to explicitly specify the names, parameters, and return values of a class's methods without providing any implementation for them. An interface is usually described as a "contract" that code in a function expects an instance object of a class that implements the interface to fulfill. JavaScript doesn't have a way to explicitly define an interface in code; you call a method, and deal with whatever the result is. (A return value of undefined or a runtime error might be that result!) So when we implement the Proxy Pattern in JavaScript, our Proxy object fulfills the "implicit" interface for our real object. In this case, we know what the interface is because there are tests, other functions outside the module, and documents describing it. The Proxy and Real Adagrams objects implement the same "implicit" interface because they satisfy the expectations of the code that uses them. If the idea of an implicit interface makes you feel uncomfortable, then you might like TypeScript.

How is the game structured?

Ink

The demo game is built with a framework called Ink, which makes it so you can develop terminal applications using React. This means you will find React concepts—props, state, jsx, etc.—used throughout the demo game code. Ink provides the services that reconcile React's render tree with the standard output of the terminal shell (for you, probably zsh or bash). If you're familiar with React, you know it's most commonly used to generate HTML on web pages. As you might guess, HTML in a browser can produce much richer visual output than the terminal can. You can tell this is true by looking at the render functions of React components that use Ink primitives. There are only a few of those primitives—Box and Spacer for layout, Text, Transform, and Newline for writing text with colors and styles, and Static to make the output stay instead of being refreshed. HTML has vastly more tags to make elements from. Nonetheless, the six tags Ink gives you to work with make it possible to create surprisingly dynamic and visual apps on the terminal, even to the point where you can use flexbox to arrange text in your Ink apps!

This demo game demonstrates how you can combine these pieces to create an interactive terminal app, but incidentally so does running the tests. The tests in this project are run by a JavaScript program called Jest, and Jest also uses Ink for its text rendering. Run the tests in watch mode (yarn test --watch) and play with the Watch Usage options to see a different Ink app in action.

App structure and folder Layout

The app itself starts at cli.js, which is where we call Ink's render method on the App component. The App component looks simple—it is a ScreenDisplayer inside a GameStateStore—but that simple definition hides all of the complexity of the whole app. Putting the ScreenDisplayer in the GameStateStore is the connection point between all of the state management code inside gamestate and all of the components inside of screens. (The rest of the custom components, which the screen components depend on, are in the components folder.) In a way, then, the gamestate is "global"—everything in the app has access to the game state via React Context. You can read the consequence of that decision in code: most of the screen components expect game state to be available to them via useGameStateContext, and even some of the reusable components (e.g. timer) expect it, too.

More details about screens, gamestate, and the reusable components can be found by going to their folders:

  • screens/: React components that represent the various "screens" that players move through during the game. The ScreenDisplayer chooses the screen based on the current state.
  • gamestate/: Reducers and actions that store and allow changes on the state of the game and its UI. This folder follows a pattern that is like redux but implemented with React's own useReducer.
  • components/: React components that can be reused. This includes simple display-only components like Button, complex input-handling components like NumberField, and more-esoteric components such as the context-providing GameStateStore.

History

This is not the first incarnation of the JS Adagrams demo game. An Architectural Decision Record describes the latest iteration as well as reasoning behind its development.