Tag Archives: score

Creating a Car Game in React – Part 5 – Levels and Time

This is the fifth part of a series (that began here).

In the last post, we added the concept of score. The car now can collect cups while avoiding trees; however, we don’t have any concept of what happens when there are no cups left.

In this post, we’ll add levels to the game, so that when you’ve collected all the cups, you move up. We’ll also introduce a time limit to make it progressively harder (as it currently stands, it’s not much of a challenge to collect the cups because you can take all day).

The source for this post is here. Again, not everything is in the post, so please refer to the repository.

Levels

Because we are creating levels, we’ll need to track the level that we’re on, so a new state property is in order:

this.state = {
	playerX: 100,
	playerY: 100,
	windowWidth: window.innerWidth,
	windowHeight: window.innerHeight,
	playerMomentum: 0,
	playerRotation: 0,
	playerVelocityX: 0,
	playerVelocityY: 0,
	playerLives: 3,
	playerCrashed: false,
	gameLoopActive: false,
	message: "",
	score: 0,
	level: 1,
	cupCount: 1, 
	remainingTime: 0
};

If you’ve followed this through from this first post, you may be asking yourself: “Is he ever going to refactor and clean this up!?”

To which I confidently respond:

“Probably!”

Anyway, you’ll notice that we have the level, the score, the time and the cup count. Advancing through the levels is conceptually just a number; here’s the code that completes a level:

completedLevel() {
	if (this.state.level >= 10) {
		this.updateMessage("Congratulations, you've completed the game");
	} 
	this.startLevel(this.state.level + 1);
}

startLevel is a slight refactor, which essentially sets the cup count and level to the new value – we’ll come back to that shortly.

You can only complete a level by collecting enough cups, so the trigger should be in the cup collection:

collectedCup(key) {
	this.setState({ 
		score: this.state.score + 1 
	});
	this.cups = this.cups.filter(cup => cup.key != key);
	this.updateMessage("Collected cup");
	if (this.cups.length == 0) {
		this.completedLevel();
	} 
}

As soon as we’re down to 0 cups, we call completedLevel.

Time

Now it’s time to have a look at the startLevel code:

startLevel(level) { 
	this.setState({
		level: level,
		cupCount: level * 2 
	}); 
	this.obstacles = this.buildObstacles(); 
	this.cups = this.placeCups();
	this.resetCarPosition();
	this.totalLevelTimeMS = (this.TOPLEVEL - (this.state.level - 1)) * 60 * 1000
	let startLevelTimeMS = (new Date()).getTime();
	this.endLevelTimeMS = startLevelTimeMS + this.totalLevelTimeMS; 
}

We’re working out when the user is out of time, and storing that in endLevelTime. Note that none of these are in state variables – the only state variable is in updated in the game loop:

let remaining = (this.endLevelTimeMS - (new Date()).getTime()) / 1000;
if (remaining <= 0) {
	this.updateMessage("Out of time!");
	this.playerDies();
}
this.setState({
	remainingTime: Math.round(remaining)
}); 

This is at the end of the game loop: we’re updating the remainingTime state variable, but first, we calculate it and, if it’s zero, the player dies (loses a life).

We need to tweak the code for the player dying, because otherwise the timer will never get reset:

playerDies() { 
	this.setState({
		playerLives: this.state.playerLives - 1,
		gameLoopActive: false
	});
	if (this.state.playerLives <= 0) {
		this.initiateNewGame();
	} else {
		this.startLevel(this.state.level);
	}
	this.repositionPlayer();
	this.setState({ 
		playerCrashed: false,
		gameLoopActive: true
	});
}

The last part is to make the time look a bit better with another of my patented icons. GameStatus.jsx should now return the following:

    return (      
        <div className="flex-container" style={flexStyle}>
            <label style={labelStyle}>
                Lives Remaining: {props.Lives}
            </label>
            <label style={labelStyle}>
                Score: {props.Score}
            </label>
            <label style={labelStyle}>
                Level: {props.Level}
            </label>            
            
            <div style={containerStyle}>  
                <img src={clockImg} style={imgStyle} />
                <div style={textDivStyle}>{props.RemainingTime}</div>
            </div>

            <label style={labelStyle}>
                {props.Message}
            </label>
        </div>  
    );

There are some new styles here so that the time appears over the clock icon:

    const containerStyle = {
        position: 'relative',
        textAlign: 'center',
        color: 'red'
    }

    const textDivStyle = {        
        position: 'absolute',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        zIndex: 1,
        fontWeight: 'bold'
    }

    const imgStyle = {
        width: '100%',
        zIndex: 0
    }

In the next part, we’ll implement a high score table.

Creating a Car Game in React – Part 4 – Score

This is the fourth part of a series (that began here). So far, we have a game where you can whizz around the screen avoiding trees. This effectively encompasses most aspects of a game of this type; that is, you can move, and there is a something preventing you.

The next step is to introduce something for the player to try to do, and give a score based on that. In our case, our player is going to try and collect cups – as per the original game. The asset I’ve created for the cup is here. For anyone following this series, you may have noticed that my artwork is a little… crap.

The source for this post is here.

Cups

The first thing that we’ve done here is added some code to place a few cups around the screen; the code for this was essentially the same as build obstacles:

placeCups() {
	let cups = [];
	const cupCount = 1;
	for (let i = 1; i <= cupCount; i++) {
		const centreX = Math.floor(Math.random() * this.state.windowWidth) + 1;
		const centreY = Math.floor(Math.random() * this.state.windowHeight) + 1; 
		cups.push(<GameItem key={i} image={cupImg} centreX={centreX} centreY={centreY} width={this.spriteWidth} height={this.spriteHeight} itemType={2} />);
	}
	return cups;
}

In a later post, I hope to do a full refactor, but for now, we have a separate function. This is rendered in the same way as the obstacles:

render() { 
	return <div onKeyDown={this.onKeyDown} tabIndex="0">
		<GameStatus Lives={this.state.playerLives} Message={this.state.message} Score={this.state.score} />
		
		<Background backgroundImage={backgroundImg}
		windowWidth={this.state.windowWidth} windowHeight={this.state.windowHeight} /> 
		
		<Car carImage={this.state.playerCrashed ? brokenCarImg : carImg} 
		centreX={this.state.playerX} centreY={this.state.playerY} 
		width={this.spriteWidth} height={this.spriteHeight} 
		rotation={this.state.playerRotation} /> 
		
		{this.obstacles} 
		
		{this.cups} 
	</div>
}

Collecting cups

In order to collect something, the player must collide with it. We need to change the collision code slightly to make it a little more re-usable:

detectAnyCollision(rect1) { 
	// Have we crashed or left the screen
	if (this.detectOutScreen(rect1)) {
		return true;
	}
	let collided = this.detectGameItemCollision(this.halfWidth, this.halfHeight, rect1, this.obstacles);
	if (collided !== undefined) {
		return true;
	}
	return false;
}

detectGameItemCollision(halfWidth, halfHeight, rect1, gameItemList) {
	const collided = gameItemList.find(a => {
		var rect2 = {
			x: a.props.centreX - halfWidth, y: a.props.centreY - halfHeight,
			width: this.spriteWidth, height: this.spriteHeight
		};
		return (this.detectCollision(rect1, rect2));
	}); 
	return collided;
}

As you can see, we now have a function that returns the item that we collided with, rather than a simple boolean. We then use this at the end of the game loop to determine whether we collided with a cup:

// Check for collected cup
const item = this.detectGameItemCollision(this.halfWidth, this.halfHeight, rect1, this.cups);
if (item !== undefined) {
	this.collectedCup(item.key);
} 

Score

There’s little point in zooming around collecting cups, if there’s no permanent record, so we need to add a score. Let’s start with a state variable in game.jsx:

this.state = {
	playerX: 100,
	playerY: 100,
	windowWidth: 1500,
	windowHeight: 1500,
	playerMomentum: 0,
	playerRotation: 0,
	playerVelocityX: 0,
	playerVelocityY: 0,
	playerLives: 3,
	playerCrashed: false,
	gameLoopActive: false,
	message: "",
	score: 0
};

And here’s the collectedCup function we mentioned a second ago:

collectedCup(key) {
	this.setState({ score: this.state.score + 1 });
	this.cups = this.cups.filter(cup => cup.key != key);
	this.updateMessage("Collected cup");
}

All we’re doing here is simply updating the score and then removing that cup from the list.

The final part is to display the score on the screen; let’s have a look at our updated GameStatus.jsx:

function GameStatus(props) {
	const flexStyle = {
		display: 'flex',
		position: 'absolute',
		zIndex: 1,
		margin: 20,
		justifyContent: 'center',
		alignItems: 'center',
		width: '100%'
	};
	const labelStyle = { 
		zIndex: 1,
		margin: 50
	};
	return ( 
		<div className="flex-container" style={flexStyle}>
			<label style={labelStyle}>
				Lives Remaining: {props.Lives}
			</label>
			<label style={labelStyle}>
				Score: {props.Score}
			</label>
			<label style={labelStyle}>
				{props.Message}
			</label>
		</div> 
	);
}

As you can see, we’re just displaying the score as part of the status.

In the next post, we’ll have a look at the concept of levels, and introduce a time limit.

Reference

https://www.w3schools.com/css/css3_flexbox.asp

Debugging Recommendations Engine

Here I wrote about how to set-up and configure the MS Azure recommendations engine.

One thing that has become painfully apparent while working with recommendations is how difficult it is to work out what has gone wrong when you don’t get any recommendations. The following is a handy check-list for the next time this happens to me… so others may, or may not find this useful*:

1. Check the model was correctly generated

Once you have produced a recommendations model, you can access that model by simply navigating to it. The url is in the following format:

{recommendations uri}/ui

For example:

https://pcmrecasd4zzx2asdf.azurewebsites.net/ui

This gives you a screen such as this:

The status (listed in the centre of the screen) tells you whether the build has finished and, if so, whether it succeeded or not.

If the build has failed, you can select that row and drill into, and find out why.

In the following example, there is a reference in the usage data, to an item that is not in the catalogue.

Other reasons that the model build may fail include invalid, corrupt or missing data in either file.

2. Check the recommendation in the interface

In order to exclude other factors in your code, you can manually interrogate the model directly by simply clicking on the “Score” link above; you will be presented with a screen such as this:

In here, you can request direct recommendations to see how the model behaves.

3. Volume

If you find that your score is consistently returning as zero, then the issue may be with the volume of usage data that you have provided. 1k** rows of usage data is the sort of volume you should be dealing with; this statistic was based on a catalogue of around 20 – 30 products.

4. Distribution

The number of users matters – for the above figures, a minimum of 15** users was necessary to get any scores back. If the data sample is across too small a user base, it won’t return anything.

Footnotes

* Although this post is written by me, and is for my benefit, I stole much of its content from wiser work colleagues.

** Arbitrary values – your mileage may vary.

Console Games – Snake – Part 5

Continuing on from my series of posts on writing a console game with my children, this post will cover the score and speed up the game a little to make it progressively harder. If you haven’t seen the earlier posts then start here.

What’s the score?

Let’s start with the score; first thing to do is create a variable to store it:

    class Program
    {
        private static int _length = 6;
        private static int _score = 0;

The way to increase the score is to eat food, so that’s quite straight-forward:

private static void DetectCollision(Position currentPos)
{
    …
    // Check if we've eaten the food
    if (_foodPosition.left == currentPos.left && _foodPosition.top == currentPos.top)
    {
        _length++;
        _score++;
        _foodPosition = null;
}

Nothing hugely complicated there. Finally, display the score:

private static void DrawScreen()
{
    Console.Clear();

    Console.SetCursorPosition(Console.WindowWidth - 3, Console.WindowHeight - 1);
    Console.Write(_score);

Speed

That’s the score; next we need to speed the game up. Currently we have an `UpdateGame()` method that determines how often the game is updated; here’s what it currently does:

        private static bool UpdateGame()
        {
            if (DateTime.Now < nextUpdate) return false;

            if (_foodPosition == null)
            {
                _foodPosition = new Position()
                {
                    left = _rnd.Next(Console.WindowWidth),
                    top = _rnd.Next(Console.WindowHeight)
                };
            }

            if (_lastKey.HasValue)
            {
                Move(_lastKey.Value);
            }

            nextUpdate = DateTime.Now.AddMilliseconds(500);
            return true;
        }

So, we can simply change the nextUpdate to use a variable that we already have; like this:

nextUpdate = DateTime.Now.AddMilliseconds(500 / (_score + 1));

Game Over

Okay, well, the eagle eyed among you may have noticed that game over just gives a runtime error; let’s try something a little more user friendly. First, we’ll create a variable to store whether the game is still in play:

        private static bool _inPlay = true;

Next, change the game loop to use this:

        static void Main(string[] args)
        {
            Console.CursorVisible = false;
            DrawScreen();
            while (_inPlay)
            {

And finally, change the `GameOver()` method:

        private static void GameOver()
        {
            _inPlay = false;
            Console.Clear();
            Console.WriteLine("Game over.");
            Console.ReadLine();
        }

Final word

I’m still working through this game, and with a catch game (which I’ll also post at some stage) with the children. The way that I’ve been addressing this is, after an initial explanation phase, asking the children to complete each small section; for example, in the above section, I would have asked them to complete three separate tasks: To create a new boolean variable, to use that variable in the while loop and to re-write the GameOver() function so that it sets the variable to false. Roughly speaking, the posts are arranged in small sections, and they could be treated as separate exercises.

Please leave a comment if you found any of these helpful, or with any suggestions for improvements.

If I get the time or the inclination, I might break these posts down into individual exercises and post that as well.