commit initial
This commit is contained in:
commit
f7abfc4902
23 changed files with 432 additions and 0 deletions
5
.dockerignore
Normal file
5
.dockerignore
Normal file
|
@ -0,0 +1,5 @@
|
|||
target/
|
||||
.git/
|
||||
.idea/
|
||||
*.class
|
||||
*.log
|
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
logs
|
||||
target
|
||||
/.bsp
|
||||
/.idea
|
||||
/.idea_modules
|
||||
/.classpath
|
||||
/.project
|
||||
/.settings
|
||||
/RUNNING_PID
|
24
Dockerfile
Normal file
24
Dockerfile
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Use the official OpenJDK base image
|
||||
FROM openjdk:11
|
||||
|
||||
# Install sbt
|
||||
RUN echo "deb https://repo.scala-sbt.org/scalasbt/debian all main" | tee /etc/apt/sources.list.d/sbt.list
|
||||
RUN echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | tee /etc/apt/sources.list.d/sbt_old.list
|
||||
RUN curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | apt-key add
|
||||
RUN apt-get update
|
||||
RUN apt-get install sbt
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the project files into the container
|
||||
COPY . /app
|
||||
|
||||
# Run sbt clean and compile
|
||||
RUN sbt clean compile
|
||||
|
||||
# Expose the port the app runs on
|
||||
EXPOSE 9000
|
||||
|
||||
# Command to run the application
|
||||
CMD ["sbt", "run"]
|
11
app/controllers/AsteroidGameController.scala
Normal file
11
app/controllers/AsteroidGameController.scala
Normal file
|
@ -0,0 +1,11 @@
|
|||
package controllers
|
||||
|
||||
import javax.inject._
|
||||
import play.api.mvc._
|
||||
|
||||
@Singleton
|
||||
class AsteroidGameController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {
|
||||
def game(): Action[AnyContent] = Action { implicit request: Request[AnyContent] =>
|
||||
Ok(views.html.game())
|
||||
}
|
||||
}
|
24
app/controllers/HomeController.scala
Normal file
24
app/controllers/HomeController.scala
Normal file
|
@ -0,0 +1,24 @@
|
|||
package controllers
|
||||
|
||||
import javax.inject._
|
||||
import play.api._
|
||||
import play.api.mvc._
|
||||
|
||||
/**
|
||||
* This controller creates an `Action` to handle HTTP requests to the
|
||||
* application's home page.
|
||||
*/
|
||||
@Singleton
|
||||
class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {
|
||||
|
||||
/**
|
||||
* Create an Action to render an HTML page.
|
||||
*
|
||||
* The configuration in the `routes` file means that this method
|
||||
* will be called when the application receives a `GET` request with
|
||||
* a path of `/`.
|
||||
*/
|
||||
def index(): Action[AnyContent] = Action { implicit request: Request[AnyContent] =>
|
||||
Ok(views.html.index())
|
||||
}
|
||||
}
|
6
app/views/game.scala.html
Normal file
6
app/views/game.scala.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
@()
|
||||
|
||||
@main("Asteroid Cookies Game") {
|
||||
<canvas id="gameCanvas" width="800" height="600"></canvas>
|
||||
<script src="@routes.Assets.versioned("javascripts/game.js")"></script>
|
||||
}
|
5
app/views/index.scala.html
Normal file
5
app/views/index.scala.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
@()
|
||||
|
||||
@main("Welcome to Play") {
|
||||
<h1>Welcome to Play!</h1>
|
||||
}
|
25
app/views/main.scala.html
Normal file
25
app/views/main.scala.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
@*
|
||||
* This template is called from the `index` template. This template
|
||||
* handles the rendering of the page header and body tags. It takes
|
||||
* two arguments, a `String` for the title of the page and an `Html`
|
||||
* object to insert into the body of the page.
|
||||
*@
|
||||
@(title: String)(content: Html)
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@* Here's where we render the page title `String`. *@
|
||||
<title>@title</title>
|
||||
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
|
||||
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
@* And here's where we render the `Html` object containing
|
||||
* the page content. *@
|
||||
@content
|
||||
|
||||
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
1
build-local.sh
Normal file
1
build-local.sh
Normal file
|
@ -0,0 +1 @@
|
|||
docker build -t asteroid-cookies .
|
16
build.sbt
Normal file
16
build.sbt
Normal file
|
@ -0,0 +1,16 @@
|
|||
name := """asteroid-cookies"""
|
||||
|
||||
version := "1.0-SNAPSHOT"
|
||||
|
||||
lazy val root = (project in file(".")).enablePlugins(PlayScala)
|
||||
|
||||
scalaVersion := "2.13.14"
|
||||
|
||||
libraryDependencies += guice
|
||||
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "7.0.0" % Test
|
||||
|
||||
// Adds additional packages into Twirl
|
||||
//TwirlKeys.templateImports += "com.example.controllers._"
|
||||
|
||||
// Adds additional packages into conf/routes
|
||||
// play.sbt.routes.RoutesKeys.routesImport += "com.example.binders._"
|
1
conf/application.conf
Normal file
1
conf/application.conf
Normal file
|
@ -0,0 +1 @@
|
|||
# https://www.playframework.com/documentation/latest/Configuration
|
50
conf/logback.xml
Normal file
50
conf/logback.xml
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<!-- https://www.playframework.com/documentation/latest/SettingsLogger -->
|
||||
|
||||
<!DOCTYPE configuration>
|
||||
|
||||
<configuration>
|
||||
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
|
||||
<import class="ch.qos.logback.classic.AsyncAppender"/>
|
||||
<import class="ch.qos.logback.core.FileAppender"/>
|
||||
<import class="ch.qos.logback.core.ConsoleAppender"/>
|
||||
|
||||
<appender name="FILE" class="FileAppender">
|
||||
<file>${application.home:-.}/logs/application.log</file>
|
||||
<encoder class="PatternLayoutEncoder">
|
||||
<charset>UTF-8</charset>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{36}) %magenta(%X{pekkoSource}) %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="STDOUT" class="ConsoleAppender">
|
||||
<!--
|
||||
On Windows, enabling Jansi is recommended to benefit from color code interpretation on DOS command prompts,
|
||||
which otherwise risk being sent ANSI escape sequences that they cannot interpret.
|
||||
See https://logback.qos.ch/manual/layouts.html#coloring
|
||||
-->
|
||||
<!-- <withJansi>true</withJansi> -->
|
||||
<encoder class="PatternLayoutEncoder">
|
||||
<charset>UTF-8</charset>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{36}) %magenta(%X{pekkoSource}) %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNCFILE" class="AsyncAppender">
|
||||
<appender-ref ref="FILE"/>
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNCSTDOUT" class="AsyncAppender">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</appender>
|
||||
|
||||
<logger name="play" level="INFO"/>
|
||||
<logger name="application" level="DEBUG"/>
|
||||
|
||||
<root>
|
||||
<appender-ref ref="ASYNCFILE"/>
|
||||
<appender-ref ref="ASYNCSTDOUT"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
11
conf/routes
Normal file
11
conf/routes
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Routes
|
||||
# This file defines all application routes (Higher priority routes first)
|
||||
# https://www.playframework.com/documentation/latest/ScalaRouting
|
||||
# ~~~~
|
||||
|
||||
# An example controller showing a sample home page
|
||||
GET / controllers.HomeController.index()
|
||||
|
||||
# Map static resources from the /public folder to the /assets URL path
|
||||
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
||||
GET /game controllers.AsteroidGameController.game()
|
1
project/build.properties
Normal file
1
project/build.properties
Normal file
|
@ -0,0 +1 @@
|
|||
sbt.version=1.10.1
|
1
project/plugins.sbt
Normal file
1
project/plugins.sbt
Normal file
|
@ -0,0 +1 @@
|
|||
addSbtPlugin("org.playframework" % "sbt-plugin" % "3.0.5")
|
BIN
public/images/cookie.png
Normal file
BIN
public/images/cookie.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
public/images/favicon.png
Normal file
BIN
public/images/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 687 B |
BIN
public/images/monster.png
Normal file
BIN
public/images/monster.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
189
public/javascripts/game.js
Normal file
189
public/javascripts/game.js
Normal file
|
@ -0,0 +1,189 @@
|
|||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
let score = 0;
|
||||
let gameOver = false;
|
||||
|
||||
class Cookie {
|
||||
constructor(x, y, size) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.size = size;
|
||||
this.speed = Math.random() * 0.5 + 0.2; // Reduced speed range
|
||||
this.angle = Math.random() * Math.PI * 2;
|
||||
this.image = new Image();
|
||||
this.image.src = '/assets/images/cookie.png';
|
||||
}
|
||||
|
||||
update() {
|
||||
this.x += Math.cos(this.angle) * this.speed;
|
||||
this.y += Math.sin(this.angle) * this.speed;
|
||||
|
||||
if (this.x < 0) this.x = canvas.width;
|
||||
if (this.x > canvas.width) this.x = 0;
|
||||
if (this.y < 0) this.y = canvas.height;
|
||||
if (this.y > canvas.height) this.y = 0;
|
||||
}
|
||||
|
||||
draw() {
|
||||
ctx.drawImage(this.image, this.x - this.size / 2, this.y - this.size / 2, this.size, this.size);
|
||||
}
|
||||
}
|
||||
|
||||
class Ship {
|
||||
constructor() {
|
||||
this.x = canvas.width / 2;
|
||||
this.y = canvas.height / 2;
|
||||
this.angle = 0;
|
||||
this.size = 40;
|
||||
this.image = new Image();
|
||||
this.image.src = '/assets/images/monster.png';
|
||||
this.speed = 0;
|
||||
this.rotationSpeed = 0.2; // Increased rotation speed
|
||||
}
|
||||
|
||||
update() {
|
||||
this.x += Math.cos(this.angle) * this.speed;
|
||||
this.y += Math.sin(this.angle) * this.speed;
|
||||
|
||||
if (this.x < 0) this.x = canvas.width;
|
||||
if (this.x > canvas.width) this.x = 0;
|
||||
if (this.y < 0) this.y = canvas.height;
|
||||
if (this.y > canvas.height) this.y = 0;
|
||||
|
||||
this.speed *= 0.98; // Add friction
|
||||
}
|
||||
|
||||
draw() {
|
||||
ctx.save();
|
||||
ctx.translate(this.x, this.y);
|
||||
ctx.rotate(this.angle);
|
||||
ctx.drawImage(this.image, -this.size / 2, -this.size / 2, this.size, this.size);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
rotate(direction) {
|
||||
this.angle += this.rotationSpeed * direction;
|
||||
}
|
||||
}
|
||||
|
||||
class Bullet {
|
||||
constructor(x, y, angle) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.angle = angle;
|
||||
this.speed = 5;
|
||||
this.size = 5;
|
||||
}
|
||||
|
||||
update() {
|
||||
this.x += Math.cos(this.angle) * this.speed;
|
||||
this.y += Math.sin(this.angle) * this.speed;
|
||||
}
|
||||
|
||||
draw() {
|
||||
ctx.fillStyle = 'yellow';
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
const cookies = [];
|
||||
const ship = new Ship();
|
||||
const bullets = [];
|
||||
|
||||
function spawnCookies() {
|
||||
if (cookies.length < 10) {
|
||||
cookies.push(new Cookie(Math.random() * canvas.width, Math.random() * canvas.height, 30));
|
||||
}
|
||||
}
|
||||
|
||||
function checkCollisions() {
|
||||
// Ship-Cookie collision
|
||||
cookies.forEach((cookie, cookieIndex) => {
|
||||
const dx = ship.x - cookie.x;
|
||||
const dy = ship.y - cookie.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < ship.size / 2 + cookie.size / 2) {
|
||||
gameOver = true;
|
||||
}
|
||||
|
||||
// Bullet-Cookie collision
|
||||
bullets.forEach((bullet, bulletIndex) => {
|
||||
const bulletDx = bullet.x - cookie.x;
|
||||
const bulletDy = bullet.y - cookie.y;
|
||||
const bulletDistance = Math.sqrt(bulletDx * bulletDx + bulletDy * bulletDy);
|
||||
|
||||
if (bulletDistance < bullet.size + cookie.size / 2) {
|
||||
cookies.splice(cookieIndex, 1);
|
||||
bullets.splice(bulletIndex, 1);
|
||||
score += 10;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function gameLoop() {
|
||||
// Set the background to black
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (gameOver) {
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.font = '48px Arial';
|
||||
ctx.fillText('Game Over', canvas.width / 2 - 100, canvas.height / 2);
|
||||
ctx.font = '24px Arial';
|
||||
ctx.fillText(`Score: ${score}`, canvas.width / 2 - 50, canvas.height / 2 + 40);
|
||||
return;
|
||||
}
|
||||
|
||||
ship.update();
|
||||
ship.draw();
|
||||
|
||||
cookies.forEach((cookie, index) => {
|
||||
cookie.update();
|
||||
cookie.draw();
|
||||
});
|
||||
|
||||
bullets.forEach((bullet, index) => {
|
||||
bullet.update();
|
||||
bullet.draw();
|
||||
|
||||
if (bullet.x < 0 || bullet.x > canvas.width || bullet.y < 0 || bullet.y > canvas.height) {
|
||||
bullets.splice(index, 1);
|
||||
}
|
||||
});
|
||||
|
||||
spawnCookies();
|
||||
checkCollisions();
|
||||
|
||||
// Draw score
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.font = '24px Arial';
|
||||
ctx.fillText(`Score: ${score}`, 10, 30);
|
||||
|
||||
requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
switch(event.key) {
|
||||
case 'ArrowLeft':
|
||||
ship.rotate(-1); // Rotate counterclockwise
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
ship.rotate(1); // Rotate clockwise
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
ship.speed = 5;
|
||||
break;
|
||||
case ' ':
|
||||
bullets.push(new Bullet(ship.x, ship.y, ship.angle));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
gameLoop();
|
||||
});
|
0
public/javascripts/main.js
Normal file
0
public/javascripts/main.js
Normal file
0
public/stylesheets/main.css
Normal file
0
public/stylesheets/main.css
Normal file
8
run-docker.sh
Normal file
8
run-docker.sh
Normal file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Stop and remove existing container with the same name
|
||||
docker stop "$(docker ps -aq --filter "name=asteroid-cookies")"
|
||||
docker rm "$(docker ps -aq --filter "name=asteroid-cookies")"
|
||||
|
||||
# Run the container with the specified tag and name
|
||||
docker run -p 9000:9000 --name asteroid-cookies asteroid-cookies
|
45
test/controllers/HomeControllerSpec.scala
Normal file
45
test/controllers/HomeControllerSpec.scala
Normal file
|
@ -0,0 +1,45 @@
|
|||
package controllers
|
||||
|
||||
import org.scalatestplus.play._
|
||||
import org.scalatestplus.play.guice._
|
||||
import play.api.test._
|
||||
import play.api.test.Helpers._
|
||||
|
||||
/**
|
||||
* Add your spec here.
|
||||
* You can mock out a whole application including requests, plugins etc.
|
||||
*
|
||||
* For more information, see https://www.playframework.com/documentation/latest/ScalaTestingWithScalaTest
|
||||
*/
|
||||
class HomeControllerSpec extends PlaySpec with GuiceOneAppPerTest with Injecting {
|
||||
|
||||
"HomeController GET" should {
|
||||
|
||||
"render the index page from a new instance of controller" in {
|
||||
val controller = new HomeController(stubControllerComponents())
|
||||
val home = controller.index().apply(FakeRequest(GET, "/"))
|
||||
|
||||
status(home) mustBe OK
|
||||
contentType(home) mustBe Some("text/html")
|
||||
contentAsString(home) must include ("Welcome to Play")
|
||||
}
|
||||
|
||||
"render the index page from the application" in {
|
||||
val controller = inject[HomeController]
|
||||
val home = controller.index().apply(FakeRequest(GET, "/"))
|
||||
|
||||
status(home) mustBe OK
|
||||
contentType(home) mustBe Some("text/html")
|
||||
contentAsString(home) must include ("Welcome to Play")
|
||||
}
|
||||
|
||||
"render the index page from the router" in {
|
||||
val request = FakeRequest(GET, "/")
|
||||
val home = route(app, request).get
|
||||
|
||||
status(home) mustBe OK
|
||||
contentType(home) mustBe Some("text/html")
|
||||
contentAsString(home) must include ("Welcome to Play")
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue