Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Developing Snake Game #46

Merged
merged 21 commits into from
Dec 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,5 @@ dist
dummy/
dummy/**

Videos_Clases/
Videos_Clases/

50 changes: 50 additions & 0 deletions kaboomjs/videojuegos/SnakeGame/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 🐍 Snake Game

Este es el [juego de la serpiente][game_url]. El jugador es una serpiente que vive en un grid. Aparecen aleatoreamente frutas que la serpiente puede comer. Cuando la serpiente come, se hace más grande, y si la serpiente llega a chocar con sigo misma, pierde.

# Link del juego

El juego de la serpiente está en [Replit][game_url]

# Temas en relación al curso

- [ ] Grid positions
- [ ] Events
- [ ] Layout
- [ ] Point system

# Programación y conceptos importantes

La "Serpiente" es solo un bloque.

Lo que podemos hacer, es que los demás bloques sigan a la cabeza y así

Para poder hacer que se sigan, podemos hacer un update de la posición del final al principio

## Juego basado en Eventos

En el juego ocurren los siguientes eventos

- Moverse
- Moverser: Cada frame dependiendo de la velocidad
- Comer
- Colisión: Cabeza - Comida
- Chocar con la cola
- Colision: Cabeza - Cuerpo

## Grid

La posición de una serpiente, puede ser un grid, entonces convendría hacer un grid

# Comentarios generales

- El movimiento va a tener que ser con render, para que sea más sencillo
- Cuando se actualiza el movimiento de la serpiente, tiene que usarse un Vec2.clone(), ya que como es un objeto, se pasa por _referencia_. Entonces se necesita el clone, para que se pase por value
- Las colisiones son muy precisas. Detectan incluso si están adyacentes. Entonces es mejor hacer un collision detection manual

# Ideas de otros lugares y links

- [https://www.edureka.co/blog/snake-game-with-pygame/](https://www.edureka.co/blog/snake-game-with-pygame/)
- [https://www.youtube.com/watch?v=QTcIXok9wNY](https://www.youtube.com/watch?v=QTcIXok9wNY)

[game_url]: https://replit.com/@EduardoGmez1/SnakeGame3#code/main.js
163 changes: 163 additions & 0 deletions kaboomjs/videojuegos/SnakeGame/code/Snake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { FabricaSnake, Grid, Snake, Direction, GameMovableElement } from './interfaces';
import { Vec2, Origin } from 'kaboom';
import { SnakeTags } from './tags';


declare function origin(o: Origin): void

/**
* Crea una serpiente, que no se sale del grid determinado
* @param grid Grid
* @param initialCoords Vec2
* @returns Snake
*/
export const newSnake: FabricaSnake = (grid: Grid, initialCoords: Vec2) => {

// Esta es la cabeza, tiene el tag de la cabeza de la serpiente
const head: GameMovableElement = add([
rect(grid.blockDimensions.width, grid.blockDimensions.height),
area(),
pos(grid.getPositionFromCoordinates(initialCoords)),
color(rgb(0, 255, 0)),
outline(1, rgb(0, 0, 0)),
SnakeTags.head,
origin("center")
])


const snake: Snake = {
direction: 'up',
hasGrown: false,
/**
* El cuerpo de la serpiente es un arreglo de arreglos.
* [SnakeBodyPart, Coords]
*
* Las coordenadas son un Vec2
*/
body: [
[head, initialCoords]
],
grow() {

// Con esto, se logra que cuando crezca la serpiente, no detecte que se está comiendo a sí misma
this.hasGrown = true;

const lastPositionCoords = this.body[this.body.length - 1][1]

const newBody: GameMovableElement = add([
rect(grid.blockDimensions.width, grid.blockDimensions.height),
area(),
pos(grid.getPositionFromCoordinates(lastPositionCoords)),
color(rgb(0, 255, 0)),
outline(1, rgb(0, 0, 0)),
SnakeTags.body,
origin("center")
])

this.body.push([newBody, lastPositionCoords])

},
move(direction: Direction) {

// Prevents the snake for going backwards
switch (direction) {
case "up":
if (this.direction === "down")
direction = "down";
break;
case "down":
if (this.direction === "up")
direction = "up";
break;
case "left":
if (this.direction === "right")
direction = "right";
break;
case "right":
if (this.direction === "left")
direction = "left";
break;
}

this.direction = direction

/**
* Este indice indica desde donde crece la serpiente.
*
* Si está creciendo (hasGrown == true), la última coordenada no se debe de mover (porque en ese momento, la última y penúltima posición estarían en la misma posición)
*
* Una vez que ya deje de crecer, la serpiente empieza desde hasta atrás a crecer
*/
const firstIndex = this.hasGrown ? this.body.length - 2 : this.body.length - 1

// Goes backwards in the snake's body and updates position
for (let i = firstIndex; i > 0; i--) {

this.body[i][1] = this.body[i - 1][1].clone() // Tiene que ser clone, porque este es un objeto, entonces se pasa por referencia
this.body[i][0].moveTo(grid.getPositionFromCoordinates(this.body[i][1]))

}

// Le dice a la serpiente que ya deje de crecer
this.hasGrown = false

// Move head
switch (this.direction) {
case "up":
this.body[0][1].y--
break;

case "down":
this.body[0][1].y++
break;

case "left":
this.body[0][1].x--
break;

case "right":
this.body[0][1].x++
break;
default:
break;
}

//Check boundaries and reset if needed
if (this.body[0][1].x < 0) {
this.body[0][1].x = grid.maxColumns - 1
}
if (this.body[0][1].x > grid.maxColumns - 1) {
this.body[0][1].x = 0
}
if (this.body[0][1].y < 0) {
this.body[0][1].y = grid.maxRows - 1
}
if (this.body[0][1].y > grid.maxRows - 1) {
this.body[0][1].y = 0
}


this.body[0][0].moveTo(grid.getPositionFromCoordinates(this.body[0][1]))


},
/**
* Destruye la serpiente, y todo su cuerpo
*/
kill() {

for (let i = 0; i < this.body.length; i++) {
const block = this.body[i];
block[0].destroy()
}

this.body = []
}
}


return snake;
}



31 changes: 31 additions & 0 deletions kaboomjs/videojuegos/SnakeGame/code/buttons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Vec2, Origin } from 'kaboom';

declare function origin(o: Origin): any

/**
* Makes a clickable text that triggers an action
* @param message string
* @param position Vec2
* @param onClickFunction () => void
*/
export function addButton(message: string, position: Vec2, onClickFunction: () => void) {

const btn = add([
text(message, {
size: 20
}),
pos(position),
area({ cursor: "pointer", }),
origin("center"),
]);

btn.clicks(onClickFunction);

btn.hovers(() => {

btn.scale = vec2(1.2);
}, () => {
btn.scale = vec2(1);
});

}
29 changes: 29 additions & 0 deletions kaboomjs/videojuegos/SnakeGame/code/gridSystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Vec2 } from 'kaboom';
import { FabricaGrid } from './interfaces';

/**
* Crea un grid, con las dimensiones del vector.
* Luego, puedes obtener las coordenadas en la pantalla, en base a una coordenada del grid, con la función de getPositionFromCoordinates.
* @param grid Vec2
* @returns Grid
*/
export const newGrid: FabricaGrid = (grid: Vec2) => {

return {
maxColumns: grid.x,
maxRows: grid.y,
blockDimensions: {
height: height() / grid.y,
width: width() / grid.x
},
getPositionFromCoordinates(coordinates: Vec2) {

return vec2(
this.blockDimensions.width * coordinates.x,
this.blockDimensions.height * coordinates.y
)


}
}
}
78 changes: 78 additions & 0 deletions kaboomjs/videojuegos/SnakeGame/code/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Character, Vec2, AreaComp } from 'kaboom';

/**
* Kaboom element, that has the function "moveTo"
*/
export type GameMovableElement = Character<AreaComp & { moveTo(v: Vec2): void }>

/**
* In game grid. Lets you determine the position of a cell in the grid.
*/
export interface Grid {
/**
* Max number of rows
*/
readonly maxRows: number,
/**
* Max number of columns
*/
readonly maxColumns: number,
/**
* Get the dimensions of the grid's cell
*/
readonly blockDimensions: {
readonly height: number,
readonly width: number
}
/**
* Given a set of coordinates, returns the position vector on the screen
* @param coordinates Vec2
*/
getPositionFromCoordinates(coordinates: Vec2): Vec2
}

/**
* Directions that the snake can move in.
*/
export type Direction = 'up' | 'down' | 'left' | 'right';

/**
* Definición de una serpiente.
*/
export interface Snake {
/**
* Direction where snake is moving
*/
direction: Direction,
/**
* Coords of the body of the snake
*/
body: [Character<AreaComp & { moveTo(v: Vec2): void }>, Vec2][],
/**
* Adds a new block to the snake
*/
grow(): void
/**
* Moves the snake in the direction it is facing
* @param direction Direction where snake is moving
*/
move(direction: Direction): void

// indicates if the snake has just gotten a new block
hasGrown: boolean

// Destroys the snake
kill(): void


}

/**
* Creates a new snake, using a grid to determine the position of the snake
*/
export type FabricaSnake = (grid: Grid, initialCoords: Vec2) => Snake

/**
* Creates a new grid
*/
export type FabricaGrid = (grid: Vec2) => Grid
Loading