Wednesday, April 20, 2022

Python - Project - Turtle Crossing Game (Day 23)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


Project - Turtle Crossing Game





Rule



1. A turtle moves forwards when you press the "Up" key. It can only move forwards, not back, left or right.
2. Cars are randomly generated along the y-axis and will move from the right edge of the screen to the left edge.
3. When the turtle hits the top edge of the screen, it moves back to the original position and the player levels up. On the next level, the car speed increases.
4. When the turtle collides with a car, it's game over and everything stops.


Break down



    1. Move the turtle with keypress
    2. Create and move the cars
    3. Detect the collisions
    4. Detect when turtle reaches the other side
    5. Create a scoreboard


Turtle Crossing Game



player.py
from turtle import Turtle

STARTING_POSITION = (0, -280)
MOVE_DISTANCE = 10
FINISH_LINE_Y = 280
UP = 90


class Player(Turtle):

    def __init__(self):
        """Constructor"""
        super().__init__()

        self.shape("turtle")
        self.penup()
        self.setheading(UP)
        self.go_to_starting_position()

    def go_to_starting_position(self):
        """Reset player's position to its starting position"""
        self.setpos(STARTING_POSITION)

    def go_up(self):
        """Move forward with defined moving distance with its
current heading"""
        self.forward(MOVE_DISTANCE)

    def is_at_finish_line(self):
        """Determine if the player reaches the finish line"""
        if self.ycor() > FINISH_LINE_Y:
            return True

        return False

scoreboard.py
from turtle import Turtle

FONT = ("Courier", 24, "normal")


class Scoreboard(Turtle):

    def __init__(self):
        """Constructor"""
        super().__init__()

        # Attributes
        self.level = 1

        self.hideturtle()
        self.penup()
        self.setpos(-200, 250)
        self.refresh_scoreboard()

    def refresh_scoreboard(self):
        """Show the latest score"""
        self.clear()
        self.write(f"Level: {self.level}", False, align="center",
font=FONT)

    def level_up(self):
        """Increase level and update the display message"""
        self.level += 1
        self.refresh_scoreboard()

    def game_over(self):
        """Show Game Over message"""
        # Move turtle to the center first
        self.setpos(0, 0)
        self.write("Game Over", False, align="center", font=FONT)
       

car_manager.py
import random
from turtle import Turtle


COLORS = ["red", "orange", "yellow", "green", "blue", "purple"]
STARTING_MOVE_DISTANCE = 5
MOVE_INCREMENT = 10
LEFT = 180


class CarManager:

    def __init__(self):
        """Constructor"""

        # Setup Attributes
        self.cars = []
        self.car_speed = STARTING_MOVE_DISTANCE

    def generate_car(self):
        """Generate a car and put it inside cars list"""
        tim = Turtle()
        tim.shape("square")
        tim.shapesize(stretch_len=2)
        tim.penup()
        tim.color(random.choice(COLORS))
        tim.setheading(LEFT)
        tim.setpos(300, random.randint(-250, 250))

        self.cars.append(tim)

    def move(self):
        """Make all cars moving with defined speed"""
        for car in self.cars:
            car.forward(self.car_speed)

    def has_collisions(self, player):
        """Determien if there is a collision between cars and player"""
        for car in self.cars:
            if car.distance(player) < 20:
                return True

        return False

    def level_up(self):
        """Increase car speed"""
        self.car_speed += MOVE_INCREMENT

main.py
import time
import random
from turtle import Screen
from player import Player
from car_manager import CarManager
from scoreboard import Scoreboard

# Init Screen
screen = Screen()
screen.setup(width=600, height=600)
# Turn turtle animation on/off and set delay for update drawings.
screen.tracer(0)

player = Player()
carManager = CarManager()
scoreboard = Scoreboard()

# Listen key Up event from screen
screen.onkey(player.go_up, "Up")
screen.listen()

game_is_on = True
while game_is_on:
    time.sleep(0.1)

    # Generate a car by random chance
    if random.randint(1, 6) == 1:
        carManager.generate_car()

    # Move all cars
    carManager.move()

    # If there are collisions
    if carManager.has_collisions(player):
        # Show Game Over message
        scoreboard.game_over()
        # Terminate the game
        game_is_on = False

    # If player reaches to the finish line
    if player.is_at_finish_line():
        # Reset the player's position
        player.go_to_starting_position()

        # Increase car speed
        carManager.level_up()

        # Refresh the scoreboard
        scoreboard.level_up()

    # Perform a TurtleScreen update
    screen.update()

screen.exitonclick()

Tuesday, April 19, 2022

Python - Project - Pong Game (Day 22)

This is a 100 Days challenge to learn a new language (Python). 100 Days of Code - The Complete Python Pro Bootcamp 

I will post some notes to motivate myself to finish this challenge.


Project - Pong Game







Break down the problem:

    1. Create the screen
    2. Create and move a paddle
    3. Create another paddle
    4. Create the ball and make it move
    5. Detect collision with wall and bounce
    6. Detect collision with paddle
    7. Detect when paddle misses
    8. Keep Scores


Design



1. One class for the divider in the middle screen
2. One class for scoreboard for both left and right team
3. One class for paddle for both left and right team
4. One class for ball


Pong Game



divider.py
from turtle import Turtle

DOWN = 270


class Divider(Turtle): 
    def __init__(self, screen_y_range):
        """Constructor"""
        super().__init__()

        self.hideturtle()
        self.color("white")
        self.penup()
        self.setheading(DOWN)
        self.setpos(0, screen_y_range)

        keep_drawing = True
        while keep_drawing:
            self.pendown()
            self.forward(10)
            self.penup()
            self.forward(10)

            if self.ycor() <= screen_y_range * -1:
                keep_drawing = False

scoreboard.py
from turtle import Turtle


class Scoreboard(Turtle):

    def __init__(self, screen_y_range):
        """Constructor"""
        super().__init__()

        # Define attributes
        self.team_a_score = 0
        self.team_b_score = 0

        self.hideturtle()
        self.color("white")
        self.penup()
        # Move it down a bit
        self.setpos(0, screen_y_range - 100)
        self.refresh_scoreboard()

    def refresh_scoreboard(self):
        """refresh_scoreboard"""

        self.clear()
        self.write(
            f"{self.team_a_score}    {self.team_b_score}",
            False,
            align="center",
            font=("Arial", 60, "normal"),
        )

    def increase_score_by_team(self, team):
        """Increase team's score and refresh the scoreboard"""
        if team == "a":
            self.team_a_score += 1
        else:
            self.team_b_score += 1

        self.refresh_scoreboard()

    def game_over(self):
        """Show Game Over message"""
        # Move turtle to center first
        self.setpos(0, 0)

        self.write(
            "Game Over",
            False,
            align="center",
            font=("Arial", 60, "normal"),
        )


paddle.py
from turtle import Turtle

# Constants
MOVING_DISTANCE = 50
UP = 90
DOWN = 270


class Paddle(Turtle):

    def __init__(self, position):
        """Constructor"""
        super().__init__()

        self.shape("square")
        self.shapesize(1.0, 5, 1)
        self.color("white")
        self.penup()
        self.setheading(UP)
        self.setpos(position)

    def move_up(self):
        """Make Paddel mvoe UP"""
        self.setheading(UP)
        self.forward(MOVING_DISTANCE)

    def move_down(self):
        """Make Paddel mvoe DOWN"""
        self.setheading(DOWN)
        self.forward(MOVING_DISTANCE)


ball.py
import random
from turtle import Turtle

STARTING_POSITION = (0, 0)
UP = 90
LEFT = 180
DOWN = 270
RIGHT = 0


class Ball(Turtle):

    def __init__(self):
        """Constructor"""
        super().__init__()

        # Attributes
        self.ball_speed = 0.1

        self.penup()
        self.color("white")
        self.shape("circle")
        self.reset()

    def move(self, move_distance):
        """make ball move forward with current heading"""
        self.forward(move_distance)

    def change_direction(self, heading):
        """change ball's heading"""
        self.setheading(heading)

    def reset(self):
        """reset ball including speed, position and heading"""
        self.ball_speed = 0.1
        self.setpos(STARTING_POSITION)
        new_heading = self.get_random_heading()
        self.change_direction(new_heading)

    def get_random_heading(self):
        """generate an random heading"""
        new_heading = random.randint(0, 360)

        # Avoid weird heading
        while (
            abs(new_heading - UP) <= 10
            or abs(new_heading - LEFT) <= 10
            or abs(new_heading - DOWN) <= 10
            or abs(new_heading - RIGHT) <= 10
        ):
            new_heading = random.randint(0, 360)

        return new_heading

    def bounce(self):
        """bounce from top or bottom wall"""
        current_heading = self.heading()
        new_heading = 360 - current_heading

        self.change_direction(new_heading)

    def pong(self):
        """pong from paddle"""
        current_heading = self.heading()
        new_heading = 540 - current_heading

        self.change_direction(new_heading)

    def speed_up(self):
        """increase ball's speed"""
        self.ball_speed *= 0.8

main.py
import time
from turtle import Screen
from divider import Divider
from scoreboard import Scoreboard
from paddle import Paddle
from ball import Ball

# Constants
MOVE_DISTANCE = 20
OFFSET = 20
SCREEN_X_RANGE = 500
SCREEN_Y_RANGE = 300
POINTS_TO_WIN = 10
TEAM_A_PADDLE_X = SCREEN_X_RANGE * -1 + 100
TEAM_B_PADDLE_X = SCREEN_X_RANGE - 100

# Setup Screen
screen = Screen()
screen.setup(SCREEN_X_RANGE * 2, SCREEN_Y_RANGE * 2)
screen.bgcolor("black")
# Turn turtle animation off
screen.tracer(0)

divier = Divider(SCREEN_Y_RANGE)
scoreboard = Scoreboard(SCREEN_Y_RANGE)
ball = Ball()
paddle_a = Paddle((TEAM_A_PADDLE_X, 0))
paddle_b = Paddle((TEAM_B_PADDLE_X, 0))

# Listen key events on Screen
screen.onkey(paddle_a.move_up, "w")
screen.onkey(paddle_a.move_down, "s")
screen.onkey(paddle_b.move_up, "Up")
screen.onkey(paddle_b.move_down, "Down")
screen.listen()

# Refresh Screen
# Perform a TurtleScreen update. To be used when tracer is turned off.
screen.update()

is_game_on = True

while is_game_on:
    # Give some delay
    time.sleep(ball.ball_speed)

    # Make ball move
    ball.move(MOVE_DISTANCE)

    # Check if the ball touch the wall behind the paddle
    if (
        ball.xcor() + OFFSET >= SCREEN_X_RANGE
        or ball.xcor() - OFFSET <= SCREEN_X_RANGE * -1
    ):
        # Update Scoreboard
        if ball.xcor() + OFFSET >= SCREEN_X_RANGE:
            scoreboard.increase_score_by_team("a")
        else:
            scoreboard.increase_score_by_team("b")

        # Determine if it is game over or not
        if (
            scoreboard.team_a_score == POINTS_TO_WIN
            or scoreboard.team_b_score == POINTS_TO_WIN
        ):
            scoreboard.game_over()
            is_game_on = False
        else:
            # Reset ball
            ball.reset()

            # Add some delay for the new turn
            time.sleep(0.5)

    # Check if the ball touch the paddle
    elif (
        ball.xcor() + OFFSET >=
TEAM_B_PADDLE_X and paddle_b.distance(ball) <= 40
    ) or (ball.xcor() - OFFSET <=
TEAM_A_PADDLE_X and paddle_a.distance(ball) <= 40):
        ball.pong()
        ball.speed_up()

    # Check if the ball touch the top and bottom wall
    elif (
        ball.ycor() + OFFSET >= SCREEN_Y_RANGE
        or ball.ycor() - OFFSET <= SCREEN_Y_RANGE * -1
    ):
        ball.bounce()

    # Refresh Screen
    # Perform a TurtleScreen update.
# To be used when tracer is turned off.
    screen.update()

screen.exitonclick()


Highlight



1. Instead of adding multiple turtle and link together as a paddle, using 'shapesize' of api


Thursday, December 2, 2021

Angular 12 - Using Google Map JavaScript API with strict Content Security Policy


We have some projects using Google Map JavaScript API with strict Content Security Policy, and they work well on both development build and production build. But, after upgrading some of them to Angular 12, we got the following errors on production build.

Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self' maps.googleapis.com". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present.

After googling, we find some helpful link below.


Let's create a new Angular project to do some experiment.


1. Using Angular CLI to create a new Angular project

 
$ ng new angular-csp-demo


2. Then, make sure the optimization tag is set to true under projects -> yours -> architect -> build -> configurations -> production at angular.json

 
"optimization": true,



 
$ npm install @angular/google-maps


4. Adding relating code for using google map

index.html - Loading the API
 
  <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY">
</script>


app.module.ts - import google map module
 
  imports: [
    BrowserModule,
    GoogleMapsModule,
],


app.component.ts - setup options

 
options: google.maps.MapOptions = {
center: { lat: 40, lng: -20 },
zoom: 4
};


app.component.html - using google map component

 
<google-map [options]="options"></google-map>


5. Setup Content Security Policy in meta tag

 
  <meta
    http-equiv="Content-Security-Policy"
    content="
      default-src 'self';
      script-src 'self' maps.googleapis.com;
      style-src 'self' 'unsafe-inline' fonts.googleapis.com;
      img-src 'self' data: maps.googleapis.com maps.gstatic.com;
      font-src 'self' fonts.gstatic.com;
      connect-src 'self' maps.googleapis.com;
    ">


6. Testing on Development Build

 
ng serve


There is not error in console.

7. Testing on Production Build

 
ng serve --configuration production


We got this error in console.


8. Changing the build configuration for production according to the links we shared at the beginning

 
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
},
"fonts": true
},


9. After that, the content security policy violating error disappear, and the page is back to normal again!!

Wednesday, December 1, 2021

RxJS - forkJoin, combineLatest, zip and withLatestFrom


forkJoin



It will emit the latest value from each source Observables ONLY after they all complete.


Ex:

 
// emit [0, 1, 2]
const s1$ = interval(1200)
.pipe(
tap(c => console.log('%s', c)),
take(3)
);

// emit [0, 1, 2, 3]
const s2$ = interval(500)
.pipe(
tap(c => console.log('\t%s', c)),
take(4)
);

forkJoin([s1$, s2$])
.subscribe(
([s1, s2]) => console.log('\t\t%s%s', s1, s2),
);


Result:


Step    s1 s2 => output
-------------------------------
1           0
2           1
3       0
4           2
5           3
6       1
7       2           23



According to the result above, the output Observable will emit a value only if all source Observable complete (such as step 7).



Combine multiple source Observables to create an output Observable which emit the latest values from each source Observables.


Ex:

 
// emit [0, 1, 2]
const s1$ = interval(1200)
.pipe(
tap(c => console.log('%s', c)),
take(3)
);

// emit [0, 1, 2, 3]
const s2$ = interval(500)
.pipe(
tap(c => console.log('\t%s', c)),
take(4)
);

combineLatest([s1$, s2$])
.subscribe(
([s1, s2]) => console.log('\t\t%s%s', s1, s2),
);


Result:


Step    s1 s2 => output
------------------------
1           0
2           1
3       0           01
4           2       02
5       1           12
6           3       13
7       2           23



According to the result above, the output Observable will not emit any values if some source Observables have not emitted values before (such as step 1 and 2).


zip



Create an output Observable, and it emits values by calculating values from each Source Observable in order. And if some source Observables have not emtited values before, then the output Observable will wait for it.


Ex:

 
// emit [0, 1, 2]
const s1$ = interval(1200)
.pipe(
tap(c => console.log('%s', c)),
take(3)
);

// emit [0, 1, 2, 3]
const s2$ = interval(500)
.pipe(
tap(c => console.log('\t%s', c)),
take(4)
);

zip(s1$, s2$)
.subscribe(
([s1, s2]) => console.log('\t\t%s%s', s1, s2),
);


Result:


Step    s1 s2 => output
------------------------
1           0
2           1
3       0           00
4           2
5           3
6       1           11
7       2           22



According to the result above, the output Observable only emit values for step 3, 6, and 7.


withLatestFrom



One source(primary) Observable will combine with another(secondary) Observable, and the primary Observable calcualte the output values only if it can find the latest values from secondary Observable.


Ex:

 
// emit [0, 1, 2]
const s1$ = interval(1200)
.pipe(
tap(c => console.log('%s', c)),
take(3)
);

// emit [0, 1, 2, 3]
const s2$ = interval(500)
.pipe(
tap(c => console.log('\t%s', c)),
take(4)
);

s1$.pipe(
withLatestFrom(s2$)
).subscribe(
([s1, s2]) => console.log('\t\t%s%s', s1, s2),
);


Result:


Step    s1 s2 => output
------------------------
1           0
2           1
3       0           01
4           2
5           3
6       1           13
7       2           23