Skip to content

Commit bce0631

Browse files
author
Jon
committedApr 1, 2017
Add basic game functionality
1 parent 016bf5a commit bce0631

28 files changed

+2054
-86
lines changed
 

‎.gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
ubuntu-xenial-16.04-cloudimg-console.log
12
.vagrant/
3+
deploy_resources/app.cfg
4+
25
src/vendor
3-
ubuntu-xenial-16.04-cloudimg-console.log
6+
src/public/api/config.php
File renamed without changes.

‎deploy_resources/app.cfg.example

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
DB_HOST=db.hostname.com
2+
DB_USER=battle_admin
3+
DB_PASS=db_password

‎deploy_resources/app_bootstrap.sh

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
source app.cfg
3+
4+
# Re-map DB host
5+
sudo sh -c "echo '\n127.0.0.1\t$DB_HOST $DB_HOST' >> /etc/hosts"
6+
7+
# Add DB User and load
8+
mysql -u root --password=blank -e "CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';"
9+
mysql -u root --password=blank -e "GRANT ALL PRIVILEGES ON * . * TO '$DB_USER'@'localhost';"
10+
mysql -u $DB_USER --password=$DB_PASS < db/init.sql
11+
12+
# Composer
13+
cd ../src
14+
composer install
15+
16+
echo '!!! App init complete, root password should now be reset to something secure'

‎deploy_resources/db/init.sql

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
2+
SET AUTOCOMMIT = 0;
3+
START TRANSACTION;
4+
SET time_zone = "+00:00";
5+
6+
DROP DATABASE IF EXISTS `bot_battle`;
7+
CREATE DATABASE IF NOT EXISTS `bot_battle` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
8+
USE `bot_battle`;
9+
10+
/* ======================= Users =============================*/
11+
--
12+
-- Table structure for table `users`
13+
--
14+
15+
CREATE TABLE `users` (
16+
`id` int(11) NOT NULL,
17+
`username` varchar(254) NOT NULL
18+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
19+
20+
--
21+
-- Indexes for table `users`
22+
--
23+
ALTER TABLE `users`
24+
ADD PRIMARY KEY (`id`),
25+
ADD UNIQUE KEY `username` (`username`);
26+
27+
--
28+
-- AUTO_INCREMENT for table `users`
29+
--
30+
ALTER TABLE `users`
31+
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
32+
33+
34+
/* ======================= Boards =============================*/
35+
36+
--
37+
-- Table structure for table `boards`
38+
--
39+
40+
CREATE TABLE `boards` (
41+
`id` int(11) NOT NULL,
42+
`width` int(11) NOT NULL,
43+
`height` int(11) NOT NULL
44+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
45+
46+
--
47+
-- Indexes for table `boards`
48+
--
49+
ALTER TABLE `boards`
50+
ADD PRIMARY KEY (`id`);
51+
52+
--
53+
-- AUTO_INCREMENT for table `boards`
54+
--
55+
ALTER TABLE `boards`
56+
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
57+
58+
59+
/* ======================= Games =============================*/
60+
61+
--
62+
-- Table structure for table `Games`
63+
--
64+
65+
CREATE TABLE `games` (
66+
`id` int(11) NOT NULL,
67+
`boards_id` int(11) NOT NULL,
68+
`difficulty` int(11) NOT NULL,
69+
`state` int(11) NOT NULL,
70+
`turn` int(11) NOT NULL,
71+
`length` int(11) NOT NULL
72+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
73+
74+
--
75+
-- Indexes for table `games`
76+
--
77+
ALTER TABLE `games`
78+
ADD PRIMARY KEY (`id`);
79+
80+
--
81+
-- AUTO_INCREMENT for table `games`
82+
--
83+
ALTER TABLE `games`
84+
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
85+
86+
ALTER TABLE `games` MODIFY COLUMN `boards_id` int(11) NOT NULL,
87+
ADD CONSTRAINT games_boards_id_fk
88+
FOREIGN KEY(`boards_id`)
89+
REFERENCES `boards`(`id`);
90+
91+
92+
/* ======================= Players =============================*/
93+
94+
--
95+
-- Table structure for table `players`
96+
--
97+
98+
CREATE TABLE `players` (
99+
`id` int(11) NOT NULL,
100+
`games_id` int(11) NOT NULL,
101+
`users_id` int(11),
102+
`x` int(11),
103+
`y` int(11)
104+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
105+
106+
--
107+
-- Indexes for table `players`
108+
--
109+
ALTER TABLE `players`
110+
ADD PRIMARY KEY (`id`);
111+
112+
--
113+
-- AUTO_INCREMENT for table `players`
114+
--
115+
ALTER TABLE `players`
116+
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
117+
118+
ALTER TABLE `players` MODIFY COLUMN `games_id` int(11) NOT NULL,
119+
ADD CONSTRAINT players_games_id_fk
120+
FOREIGN KEY(`games_id`)
121+
REFERENCES `games`(`id`);
122+
123+
ALTER TABLE `players` MODIFY COLUMN `users_id` int(11),
124+
ADD CONSTRAINT players_users_id_fk
125+
FOREIGN KEY(`users_id`)
126+
REFERENCES `users`(`id`);
127+
128+
/* ======================= Tiles =============================*/
129+
130+
--
131+
-- Table structure for table `tiles`
132+
--
133+
134+
CREATE TABLE `tiles` (
135+
`id` int(11) NOT NULL,
136+
`boards_id` int(11) NOT NULL,
137+
`players_id` int(11),
138+
`x` int(11),
139+
`y` int(11)
140+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
141+
142+
--
143+
-- Indexes for table `tiles`
144+
--
145+
ALTER TABLE `tiles`
146+
ADD PRIMARY KEY (`id`);
147+
148+
--
149+
-- AUTO_INCREMENT for table `tiles`
150+
--
151+
ALTER TABLE `tiles`
152+
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
153+
154+
ALTER TABLE `tiles` MODIFY COLUMN `boards_id` int(11) NOT NULL,
155+
ADD CONSTRAINT tiles_boards_id_fk
156+
FOREIGN KEY(`boards_id`)
157+
REFERENCES `boards`(`id`);
158+
159+
ALTER TABLE `tiles` MODIFY COLUMN `players_id` int(11),
160+
ADD CONSTRAINT tiles_players_id_fk
161+
FOREIGN KEY(`players_id`)
162+
REFERENCES `players`(`id`);
163+
164+
165+
166+
/* ======================= Moves =============================*/
167+
168+
--
169+
-- Table structure for table `moves`
170+
--
171+
172+
CREATE TABLE `moves` (
173+
`id` int(11) NOT NULL,
174+
`games_id` int(11) NOT NULL,
175+
`players_id` int(11) NOT NULL,
176+
`turn` int(11) NOT NULL,
177+
`action` int(11) NOT NULL
178+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
179+
180+
--
181+
-- Indexes for table `moves`
182+
--
183+
ALTER TABLE `moves`
184+
ADD PRIMARY KEY (`id`),
185+
ADD UNIQUE `games_id_users_id_turn` (`games_id`, `players_id`, `turn`);
186+
187+
--
188+
-- AUTO_INCREMENT for table `moves`
189+
--
190+
ALTER TABLE `moves`
191+
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
192+
193+
ALTER TABLE `moves` MODIFY COLUMN `games_id` int(11) NOT NULL,
194+
ADD CONSTRAINT moves_games_id_fk
195+
FOREIGN KEY(`games_id`)
196+
REFERENCES `games`(`id`);
197+
198+
ALTER TABLE `moves` MODIFY COLUMN `players_id` int(11) NOT NULL,
199+
ADD CONSTRAINT moves_players_id_fk
200+
FOREIGN KEY(`players_id`)
201+
REFERENCES `players`(`id`);
202+
203+
204+
COMMIT;

‎deploy_resources/db/reload.sh

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
source ../app.cfg
2+
mysql -u $DB_USER --password=$DB_PASS < init.sql

‎deploy_resources/vagrant_bootstrap.sh

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#!/usr/bin/env bash
22
sudo apt-get update
33

4-
54
### System level items
65
# Apache
76
sudo apt-get install -y apache2
@@ -10,6 +9,8 @@ sudo apt-get install -y php
109
sudo apt-get install -y php-mysql
1110
sudo apt-get install -y php-curl php-json php-cgi libapache2-mod-php
1211
sudo apt-get install -y php-gd
12+
# Python
13+
sudo apt-get install -y python
1314
# MySQL
1415
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password password blank'
1516
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password blank'
@@ -21,7 +22,7 @@ sudo apt-get install -y zip
2122
### Configure apache
2223
sudo systemctl enable apache2
2324
sudo rm -f /etc/apache2/sites-enabled/*
24-
sudo cp /vagrant/deploy_resources/project.conf /etc/apache2/sites-enabled/
25+
sudo cp /vagrant/deploy_resources/apache_site.conf /etc/apache2/sites-enabled/project.conf
2526
sudo a2enmod rewrite
2627
sudo systemctl start apache2
2728

‎src/lib/BotBattle.php

+29-18
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
11
<?php namespace BotBattle;
22

3-
use BotBattle\Models\Board;
4-
use BotBattle\Models\Player;
3+
use BotBattle\Config;
4+
use BotBattle\Data\Db;
55

6-
class BotBattle implements \JsonSerializable {
7-
public $board;
8-
public $players;
6+
use BotBattle\Services\UserService;
7+
use BotBattle\Services\GameService;
98

10-
function __construct(){
11-
// TODO: Load state
12-
$this->board = new Board(10, 10);
13-
$this->players = [
14-
new Player('Bob', 0, 0),
15-
new Player('Steve', 3,3)
16-
];
17-
}
9+
/**
10+
* The BotBattle main entry point
11+
*/
12+
class BotBattle {
13+
14+
private $config;
15+
private $db;
16+
17+
public $userService;
18+
public $gameService;
19+
20+
/**
21+
* Constructor
22+
* @param Config $config The configuration to use
23+
*/
24+
public function __construct(Config $config){
25+
$this->config = $config;
26+
$this->db = new Db(
27+
$this->config->get(Config::DB_HOST),
28+
$this->config->get(Config::DB_DATABASE),
29+
$this->config->get(Config::DB_USER),
30+
$this->config->get(Config::DB_PASS)
31+
);
1832

19-
public function jsonSerialize() {
20-
return [
21-
'board' => $this->board,
22-
'players' => $this->players
23-
];
33+
$this->userService = new UserService($this->db);
34+
$this->gameService = new GameService($this->db, $this->userService);
2435
}
2536
}

‎src/lib/Config.php

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php namespace BotBattle;
2+
3+
/**
4+
* Configuration storage and item definitions
5+
*/
6+
class Config {
7+
private $config;
8+
9+
const DB_HOST = 'db_host';
10+
const DB_DATABASE = 'db_database';
11+
const DB_USER = 'db_user';
12+
const DB_PASS = 'db_pass';
13+
14+
/**
15+
* Constructor
16+
* @param array $config The configuration items to store
17+
*/
18+
public function __construct(array $config){
19+
$this->config = $config;
20+
}
21+
22+
/**
23+
* Get a configuration item
24+
* @param string $itemId The ID of the configuration item
25+
*
26+
* @return mixed The value of configuration item
27+
*/
28+
public function get(string $itemId) {
29+
if (isset($this->config[$itemId])) {
30+
return $this->config[$itemId];
31+
}
32+
else {
33+
throw new \Exception("Attempting to access unset config item: $itemId");
34+
}
35+
}
36+
}

‎src/lib/Data/Db.php

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php namespace BotBattle\Data;
2+
3+
use \BotBattle\Data\DbResult;
4+
use \BotBattle\Data\DbError;
5+
6+
/**
7+
* Handles database communication
8+
*/
9+
class Db {
10+
private $connection = null;
11+
12+
/**
13+
* Constructor
14+
* @param string $host The hostname or IP address of the database server
15+
* @param string $db The name of the database
16+
* @param string $user The username to authenticate with
17+
* @param string $pass The password to authenticate with
18+
*/
19+
public function __construct(string $host, string $db, string $user, string $pass) {
20+
try {
21+
$this->connection = new \PDO("mysql:host=$host;dbname=$db", $user, $pass);
22+
// Avoiding emulation - required to have int returned
23+
$this->connection->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
24+
// Set error mode to exception
25+
$this->connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
26+
}
27+
catch (Exception $ex) {
28+
die($ex->getMessage());
29+
}
30+
}
31+
32+
/**
33+
* Query the database
34+
* @param string $query The SQL query to run
35+
* @param array $params Any parameters that should be bound to the statement
36+
*
37+
* @return DbResult The result of the query
38+
*/
39+
public function query(string $query, array $params = null) {
40+
$statement = $this->connection->prepare($query);
41+
42+
if ($statement->execute($params)) {
43+
return new DbResult($statement);
44+
}
45+
else {
46+
$error = new DbError($dbErr[0], $dbErr[1] . ': ' . $dbErr[2]);
47+
return new DbResult($statement, $error);
48+
}
49+
}
50+
51+
/**
52+
* Get the ID of the last inserted row
53+
* @return int The ID of the last inserted row
54+
*/
55+
public function insertId() {
56+
return intval($this->connection->lastInsertId());
57+
}
58+
59+
/**
60+
* Start a transaction
61+
*/
62+
public function beginTransaction() {
63+
$this->connection->beginTransaction();
64+
}
65+
66+
/**
67+
* Commit a transaction
68+
*/
69+
public function commit() {
70+
$this->connection->commit();
71+
}
72+
73+
/**
74+
* Roll back a transaction
75+
*/
76+
public function rollBack() {
77+
$this->connection->rollBack();
78+
}
79+
}

‎src/lib/Data/DbError.php

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php namespace BotBattle\Data;
2+
3+
/**
4+
* Database error
5+
*/
6+
class DbError {
7+
8+
public $code;
9+
public $message;
10+
11+
/**
12+
* Constructor
13+
* @param string $code The error code
14+
* @param string $message The error message
15+
*/
16+
public function __construct(string $code, string $message = null) {
17+
$this->code = $code;
18+
$this->message = $message;
19+
}
20+
21+
}

‎src/lib/Data/DbResult.php

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php namespace BotBattle\Data;
2+
3+
/**
4+
* Database query result
5+
*/
6+
class DbResult {
7+
8+
public $statement;
9+
public $error;
10+
11+
/**
12+
* Constructor
13+
* @param PDOStatement $statement The statement with the query result
14+
* @param DbError $error The error if there was one
15+
*/
16+
public function __construct(\PDOStatement $statement, DbError $error = null) {
17+
$this->statement = $statement;
18+
$this->error = $error;
19+
}
20+
21+
}

‎src/lib/Models/Board.php

+44-9
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,58 @@
11
<?php namespace BotBattle\Models;
22

3-
class Board implements \JsonSerializable {
3+
use BotBattle\Models\Generic\Model;
4+
5+
/**
6+
* A game board, holds all of the tiles
7+
*/
8+
class Board extends Model implements \JsonSerializable {
49
public $tiles;
10+
public $width;
11+
public $height;
512

6-
function __construct(int $width, int $height){
7-
$tiles = [];
8-
for ($x=0; $x<$width; $x++) {
9-
$tiles[$x] = [];
10-
for ($y=0; $y<$height; $y++) {
11-
$tiles[$x][$y] = new Tile($x, $y);
12-
}
13-
}
13+
private $tileMap;
1414

15+
/**
16+
* Constructor
17+
* @param int $id The ID of the board
18+
* @param int $width The width of the board
19+
* @param int $height The height of the board
20+
* @param array $tiles The tiles within the board
21+
*/
22+
public function __construct(int $id, int $width, int $height, array $tiles){
23+
parent::__construct($id);
24+
25+
$this->width = $width;
26+
$this->height = $height;
1527
$this->tiles = $tiles;
28+
29+
$this->tileMap = [];
30+
foreach($tiles as $tile) {
31+
$this->tileMap[$tile->x][$tile->y] = $tile;
32+
}
33+
}
34+
35+
/**
36+
* Get a tile at a given location
37+
* @param int $x The x coordinate of the tile
38+
* @param int $y The y coordinate of the tile
39+
*
40+
* @return Tile The tile at the given location
41+
*/
42+
public function getTileAt(int $x, int $y) {
43+
return $this->tileMap[$x][$y];
1644
}
1745

1846

47+
/**
48+
* Serializes the object to a value that can be serialized
49+
* @return array Indexed array of exposed values to be serialized
50+
*/
1951
public function jsonSerialize() {
2052
return [
53+
'id' => $this->id,
54+
'width' => $this->width,
55+
'height' => $this->height,
2156
'tiles' => $this->tiles
2257
];
2358
}

‎src/lib/Models/Error.php

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php namespace BotBattle\Models;
2+
3+
/**
4+
* An error that can be serialized
5+
*/
6+
class Error implements \JsonSerializable {
7+
private $message;
8+
9+
/**
10+
* Constructor
11+
* @param string $message The error message
12+
*/
13+
public function __construct(string $message){
14+
$this->message = $message;
15+
}
16+
17+
18+
/**
19+
* Serializes the object to a value that can be serialized
20+
* @return array Indexed array of exposed values to be serialized
21+
*/
22+
public function jsonSerialize() {
23+
return [
24+
'message' => $this->message
25+
];
26+
}
27+
}

‎src/lib/Models/Game.php

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php namespace BotBattle\Models;
2+
3+
use BotBattle\Models\Generic\Model;
4+
5+
use BotBattle\Models\Board;
6+
use BotBattle\Models\Player;
7+
use BotBattle\Models\User;
8+
9+
/**
10+
* A game - tracks the state including players, turns, and the board
11+
*/
12+
class Game extends Model implements \JsonSerializable {
13+
14+
const STATE_WAITING = 0;
15+
const STATE_RUNNING = 1;
16+
const STATE_DONE = 2;
17+
18+
public $board;
19+
public $players;
20+
21+
public $difficulty;
22+
public $turn;
23+
public $state;
24+
public $length;
25+
26+
/**
27+
* Constructor
28+
* @param int $id The ID of the game
29+
* @param Board $board The board with the tiles for the game
30+
* @param array $players The players that have joined the game
31+
* @param int $difficulty The difficulty of the game
32+
* @param int $length The length of the game (total number of turns)
33+
* @param int $state The current state of the game - should be one of Game::STATE_WAITING, Game::STATE_RUNNING, Game::STATE_DONE
34+
* @param int $turn The current turn of the game
35+
*/
36+
public function __construct(int $id, Board $board, array $players, int $difficulty, int $length=500, int $state = 0, int $turn = 0){
37+
parent::__construct($id);
38+
39+
$this->board = $board;
40+
$this->players = $players;
41+
42+
$this->difficulty = $difficulty;
43+
$this->state = $state;
44+
$this->turn = $turn;
45+
$this->length = $length;
46+
}
47+
48+
/**
49+
* Get the player of for the given user
50+
* @param User $user The user to lookup the player for
51+
*
52+
* @return Player|null The player or null if the one isn't found for the given user (the user hasn't joined the game)
53+
*/
54+
public function getUserPlayer(User $user) { // : ?Player {
55+
foreach ($this->players as $player) {
56+
if ($player->user->getId() == $user->getId()) {
57+
return $player;
58+
}
59+
}
60+
61+
return null;
62+
}
63+
64+
/**
65+
* Serializes the object to a value that can be serialized
66+
* @return array Indexed array of exposed values to be serialized
67+
*/
68+
public function jsonSerialize() {
69+
return [
70+
'id' => $this->id,
71+
'board' => $this->board,
72+
'players' => $this->players,
73+
'difficulty' => $this->difficulty,
74+
'state' => $this->state,
75+
'turn' => $this->turn,
76+
'length' => $this->length
77+
];
78+
}
79+
}

‎src/lib/Models/Generic/Model.php

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php namespace BotBattle\Models\Generic;
2+
3+
/**
4+
* Base model
5+
*/
6+
abstract class Model {
7+
protected $id;
8+
9+
/**
10+
* Constructor
11+
* @param int $id The ID of the model
12+
*/
13+
function __construct(int $id){
14+
$this->id = $id;
15+
}
16+
17+
/**
18+
* Get the ID of the model
19+
* @return int The ID of the model
20+
*/
21+
public function getId() {
22+
return $this->id;
23+
}
24+
}

‎src/lib/Models/Move.php

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php namespace BotBattle\Models;
2+
3+
use BotBattle\Models\Generic\Model;
4+
5+
/**
6+
* A player's move within a game
7+
*/
8+
class Move extends Model implements \JsonSerializable{
9+
10+
const ACTION_NONE = 0;
11+
const ACTION_LEFT = 1;
12+
const ACTION_RIGHT = 2;
13+
const ACTION_UP = 3;
14+
const ACTION_DOWN = 4;
15+
16+
public $player;
17+
public $action;
18+
public $turn;
19+
20+
/**
21+
* Constructor
22+
* @param int $id The ID of the move
23+
* @param Player $player The player that made the move
24+
* @param int $turn The turn within the game that the move was made
25+
* @param int $action The action that was performed - should be one of MOVE::ACTION_NONE, MOVE::ACTION_LEFT, MOVE::ACTION_RIGHT, MOVE::ACTION_UP, MOVE::ACTION_DOWN
26+
*/
27+
public function __construct(int $id, Player $player, int $turn, int $action){
28+
parent::__construct($id);
29+
30+
$this->player = $player;
31+
$this->action = $action;
32+
$this->turn = $turn;
33+
}
34+
35+
/**
36+
* Serializes the object to a value that can be serialized
37+
* @return array Indexed array of exposed values to be serialized
38+
*/
39+
public function jsonSerialize() {
40+
return [
41+
'id' => $this->id,
42+
'player' => $this->player,
43+
'action' => $this->action,
44+
'turn' => $this->turn
45+
];
46+
}
47+
}

‎src/lib/Models/Player.php

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,38 @@
11
<?php namespace BotBattle\Models;
22

3-
class Player implements \JsonSerializable{
4-
public $name;
3+
use BotBattle\Models\Generic\Model;
4+
5+
/**
6+
* A player within a game - a user can be a player in multiple games
7+
*/
8+
class Player extends Model implements \JsonSerializable{
9+
public $user;
510
public $x;
611
public $y;
712

8-
function __construct(string $name, int $x, int $y){
9-
$this->name = $name;
13+
/**
14+
* Constructor
15+
* @param int $id The ID of the player
16+
* @param User $user The user the player is for
17+
* @param int $x The x coordinate of the player
18+
* @param int $y The y coordinate of the player
19+
*/
20+
public function __construct(int $id, User $user, int $x, int $y){
21+
parent::__construct($id);
22+
23+
$this->user = $user;
1024
$this->x = $x;
1125
$this->y = $y;
1226
}
1327

28+
/**
29+
* Serializes the object to a value that can be serialized
30+
* @return array Indexed array of exposed values to be serialized
31+
*/
1432
public function jsonSerialize() {
1533
return [
16-
'name' => $this->name,
34+
'id' => $this->id,
35+
'user' => $this->user,
1736
'x' => $this->x,
1837
'y' => $this->y
1938
];

‎src/lib/Models/Tile.php

+24-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,38 @@
11
<?php namespace BotBattle\Models;
22

3-
class Tile implements \JsonSerializable {
3+
use BotBattle\Models\Generic\Model;
4+
5+
/**
6+
* A tile on a game board
7+
*/
8+
class Tile extends Model implements \JsonSerializable {
49
public $x;
510
public $y;
6-
public $owner;
11+
public $player;
12+
13+
/**
14+
* Constructor
15+
* @param int $id The ID of the tile
16+
* @param int $x The x coordinate of the tile
17+
* @param int $y The y coordinate of the tile
18+
* @param Player $player The player that owns the tile if there is one
19+
*/
20+
public function __construct(int $id, int $x, int $y, Player $player = null){
21+
parent::__construct($id);
722

8-
function __construct(int $x, int $y){
923
$this->x = $x;
1024
$this->y = $y;
25+
$this->player = $player;
1126
}
1227

28+
/**
29+
* Serializes the object to a value that can be serialized
30+
* @return array Indexed array of exposed values to be serialized
31+
*/
1332
public function jsonSerialize() {
1433
return [
15-
'owner' => rand(0,1),//$this->owner,
34+
'id' => $this->id,
35+
'player' => $this->player,
1636
'x' => $this->x,
1737
'y' => $this->y
1838
];

‎src/lib/Models/User.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php namespace BotBattle\Models;
2+
3+
use BotBattle\Models\Generic\Model;
4+
5+
/**
6+
* A user
7+
*/
8+
class User extends Model implements \JsonSerializable{
9+
public $id;
10+
public $username;
11+
12+
/**
13+
* Constructor
14+
* @param int $id The ID of the user
15+
* @param string $username The user's name
16+
*/
17+
public function __construct(int $id, string $username){
18+
parent::__construct($id);
19+
20+
$this->username = $username;
21+
}
22+
23+
/**
24+
* Serializes the object to a value that can be serialized
25+
* @return array Indexed array of exposed values to be serialized
26+
*/
27+
public function jsonSerialize() {
28+
return [
29+
'id' => $this->id,
30+
'username' => $this->username
31+
];
32+
}
33+
}

‎src/lib/Services/GameService.php

+710
Large diffs are not rendered by default.

‎src/lib/Services/UserService.php

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php namespace BotBattle\Services;
2+
3+
use BotBattle\Data\Db;
4+
5+
use BotBattle\Models\User;
6+
7+
/**
8+
* Handles users interactions
9+
*/
10+
class UserService {
11+
private $db;
12+
13+
/**
14+
* Constructor
15+
* @param Db $db The database connection to use
16+
*/
17+
public function __construct(Db $db) {
18+
$this->db = $db;
19+
}
20+
21+
/**
22+
* Log in as a user
23+
* @param string $username The user to log in as
24+
*
25+
* @return User The user
26+
*/
27+
public function login(string $username) { // : ?User
28+
$user = $this->getUserByUsername($username);
29+
30+
if ($user == null) {
31+
$user = $this->addUser($username);
32+
}
33+
34+
// User is still null after create, something went wrong
35+
if ($user == null) {
36+
throw new \Exception('Failed to create user');
37+
}
38+
39+
$_SESSION['user'] = $user;
40+
41+
return $user;
42+
}
43+
44+
/**
45+
* Get the session's current user
46+
* @return User|null The session's current user
47+
*/
48+
public function getCurrentUser() { // : ?User
49+
if (isset($_SESSION['user'])) {
50+
return $_SESSION['user'];
51+
}
52+
else {
53+
return null;
54+
}
55+
}
56+
57+
/**
58+
* Get a user by username
59+
* @param string $username The username of the user
60+
*
61+
* @return User|null The user with the given username if there is one
62+
*/
63+
public function getUserByUsername(string $username) { // : ?User
64+
$qry = "SELECT * FROM users WHERE username = :username";
65+
$params = [
66+
'username' => $username
67+
];
68+
$result = $this->db->query($qry, $params);
69+
70+
if ($result->error !== null) {
71+
throw new \Exception($result->error);
72+
}
73+
74+
if ($row = $result->statement->fetch(\PDO::FETCH_ASSOC)) {
75+
return new User($row['id'], $row['username']);
76+
}
77+
78+
return null;
79+
}
80+
81+
/**
82+
* Get a user
83+
* @param int $id The ID of the user
84+
*
85+
* @return User|null The user
86+
*/
87+
public function getUser(int $id) { // : ?User
88+
$qry = "SELECT * FROM users WHERE id = :id";
89+
$params = [
90+
'id' => $id
91+
];
92+
$result = $this->db->query($qry, $params);
93+
94+
if ($result->error !== null) {
95+
throw new \Exception($result->error);
96+
}
97+
98+
if ($row = $result->statement->fetch(\PDO::FETCH_ASSOC)) {
99+
return new User($row['id'], $row['username']);
100+
}
101+
102+
return null;
103+
}
104+
105+
/**
106+
* Add a new user
107+
* @param string $username The username of the user
108+
*
109+
* @return User The added user
110+
*/
111+
public function addUser(string $username) { // : ?User
112+
// Create new user
113+
$qry = "INSERT INTO users (username) VALUES (:username)";
114+
$params = [
115+
'username' => $username
116+
];
117+
$result = $this->db->query($qry, $params);
118+
119+
if ($result->error !== null) {
120+
throw new \Exception($result->error);
121+
}
122+
123+
return $this->getUserByUsername($username);
124+
}
125+
126+
/**
127+
* Delete a user
128+
* @param User $user The user to delete
129+
*
130+
* @return bool Whether deletion was succesful
131+
*/
132+
public function deleteUser(User $user) : bool {
133+
$qry = "DELETE FROM users WHERE id = :id";
134+
$params = [
135+
'id' => $user->getId()
136+
];
137+
$result = $this->db->query($qry, $params);
138+
139+
if ($result->error !== null) {
140+
throw new \Exception($result->error);
141+
}
142+
143+
return true;
144+
}
145+
}

‎src/public/api/config.php.example

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
require_once __DIR__ . '/../../vendor/autoload.php';
3+
use \BotBattle\Config;
4+
5+
return [
6+
Config::DB_HOST => 'db.hostname.com',
7+
Config::DB_USER => 'battle_admin',
8+
Config::DB_PASS => 'db_password'
9+
];

‎src/public/api/index.php

+148-32
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@
55
use \Psr\Http\Message\ResponseInterface as Response;
66

77
use \BotBattle\BotBattle;
8+
use \BotBattle\Config;
9+
10+
use \BotBattle\Models\Error;
11+
use \BotBattle\Models\Game;
812

913
session_start();
1014

15+
// Load the config and app
16+
$config = new Config(include('config.php'));
17+
$botBattle = new BotBattle($config);
18+
1119
// Setup Slim
1220
$configuration = [
1321
'settings' => [
@@ -17,66 +25,174 @@
1725
$container = new \Slim\Container($configuration);
1826
$app = new \Slim\App($container);
1927

20-
$app->group("/user", function () {
21-
$this->post('[/]', function (Request $request, Response $response) {
28+
/**
29+
* User requests
30+
*/
31+
$app->group("/users", function () use ($botBattle) {
32+
$this->post('[/]', function (Request $request, Response $response) use ($botBattle) {
2233
$json = $request->getBody();
34+
35+
// TODO: Deserialize to a request type
2336
$data = json_decode($json, true);
2437

2538
// Check data is given
2639
if (empty($data)) {
27-
error_log("Empty Data");
28-
return $response->withStatus(400);
40+
return $response->withJson(new Error('No user data sent'), 400);
2941
}
3042

31-
$_SESSION['username'] = $data['username'];
43+
$user = $botBattle->userService->login($data['username']);
3244

33-
$json = ['username' => $_SESSION['username']];
45+
return $response->withJson($user);
46+
});
47+
48+
$this->get('[/]', function (Request $request, Response $response) use ($botBattle) {
49+
$user = $botBattle->userService->getCurrentUser();
3450

35-
return $response->withJson($json);
51+
return $response->withJson($user);
3652
});
3753

38-
$this->get('[/]', function (Request $request, Response $response) {
39-
$json = ['username' => $_SESSION['username']];
54+
$this->get('/{id}', function (Request $request, Response $response) use ($botBattle) {
55+
$id = $request->getAttribute('id');
56+
$user = $botBattle->userService->getUser($id);
57+
58+
// User not found
59+
if ($user === null) {
60+
return $response->withJson(new Error('User not found'), 404);
61+
}
4062

41-
return $response->withJson($json);
63+
return $response->withJson($user);
4264
});
4365
});
4466

45-
$app->group("/action", function () {
46-
$this->post('[/]', function (Request $request, Response $response) {
67+
/**
68+
* Game requests
69+
*/
70+
$app->group("/games", function () use ($botBattle) {
71+
/**
72+
* Create a new game or get an open matching one
73+
*/
74+
$this->post('[/]', function (Request $request, Response $response) use ($botBattle) {
4775
$json = $request->getBody();
76+
77+
// TODO: Deserialize to a request type
4878
$data = json_decode($json, true);
4979

5080
// Check data is given
5181
if (empty($data)) {
52-
error_log("Empty Data");
53-
return $response->withStatus(400);
82+
return $response->withJson(new Error('No game data sent'), 400);
83+
}
84+
85+
// Create the game
86+
$game = $botBattle->gameService->createGame($data['difficulty']);
87+
88+
return $response->withJson($game);
89+
});
90+
91+
92+
/**
93+
* Get current games
94+
*/
95+
$this->get('[/]', function (Request $request, Response $response) use ($botBattle) {
96+
// TODO: List games
97+
return $response->withJson(new Error('Method not implemented'), 400);
98+
});
99+
100+
/**
101+
* Get a game by ID
102+
*/
103+
$this->get('/{id}', function (Request $request, Response $response) use ($botBattle) {
104+
$id = $request->getAttribute('id');
105+
$game = $botBattle->gameService->getGame($id);
106+
107+
// Game not found
108+
if ($game === null) {
109+
return $response->withJson(new Error('Game not found'), 404);
110+
}
111+
112+
return $response->withJson($game);
113+
});
114+
115+
/**
116+
* Join a game
117+
*/
118+
$this->post('/{id}/players', function (Request $request, Response $response) use ($botBattle) {
119+
$user = $botBattle->userService->getCurrentUser();
120+
if ($user == null) {
121+
return $response->withJson(new Error('Not logged in'), 401);
122+
}
123+
124+
$id = $request->getAttribute('id');
125+
$game = $botBattle->gameService->getGame($id);
126+
127+
// Game not found
128+
if ($game === null) {
129+
return $response->withJson(new Error('Game not found'), 404);
54130
}
55131

56-
switch ($data['action']) {
57-
case 'left':
58-
break;
59-
case 'right':
60-
break;
61-
case 'up':
62-
break;
63-
case 'down':
64-
break;
65-
default:
66-
// noop
67-
break;
132+
$player = $game->getUserPlayer($user);
133+
if ($player == null) {
134+
$player = $botBattle->gameService->joinGame($game, $user);
68135
}
69136

70-
// Return the current state
71-
$state = new BotBattle();
137+
return $response->withJson($player);
138+
});
139+
140+
/**
141+
* Make a move in a game
142+
*/
143+
$this->post('/{id}/moves', function (Request $request, Response $response) use ($botBattle) {
144+
$user = $botBattle->userService->getCurrentUser();
145+
if ($user == null) {
146+
return $response->withJson(new Error('Not logged in'), 401);
147+
}
148+
149+
$id = $request->getAttribute('id');
150+
$game = $botBattle->gameService->getGame($id);
151+
152+
// Game not found
153+
if ($game === null) {
154+
return $response->withJson(new Error('Game not found'), 404);
155+
}
156+
157+
if ($game->state === Game::STATE_WAITING) {
158+
return $response->withJson(new Error('Game hasn\'t started'), 400);
159+
}
160+
if ($game->state === Game::STATE_DONE) {
161+
return $response->withJson(new Error('Game has ended'), 400);
162+
}
163+
164+
$json = $request->getBody();
165+
166+
// TODO: Deserialize to a request type
167+
$data = json_decode($json, true);
168+
169+
// Check data is given
170+
if (empty($data)) {
171+
return $response->withJson(new Error('No move data sent'), 400);
172+
}
173+
174+
// Get the user's player
175+
$user = $botBattle->userService->getCurrentUser();
176+
$player = $game->getUserPlayer($user);
177+
178+
if ($player === null) {
179+
return $response->withJson(new Error('Current user is not a player in the game'), 400);
180+
}
181+
182+
$action = intval($data['action']);
183+
$move = $botBattle->gameService->makeMove($game, $player, $action);
184+
185+
// Move failed
186+
if ($move === null) {
187+
return $response->withJson(new Error('Invalid move action'), 400);
188+
}
72189

73-
return $response->withJson($state);
190+
return $response->withJson($move);
74191
});
75192
});
76193

77-
$app->get('[/]', function (Request $request, Response $response) {
78-
$state = new BotBattle();
79-
return $response->withJson($state);
194+
$app->get('[/]', function (Request $request, Response $response) use ($botBattle) {
195+
return $response->withJson(['BATTLEBOTS']);
80196
});
81197

82198
$app->run();

‎src/public/index.php

+108-15
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,18 @@
88
99
$(function(){
1010
pageObject = new BotBattle(800, 800);
11+
12+
$('#viewGame').click(function() {
13+
var gameId = $('#gameId').val();
14+
pageObject.viewGame(gameId);
15+
});
1116
});
1217
18+
/**
19+
* BotBattle viewer
20+
*/
1321
function BotBattle(width, height) {
22+
this.gameId = null;
1423
this.running = false;
1524
this.width = width;
1625
this.height = height;
@@ -19,60 +28,129 @@ function BotBattle(width, height) {
1928
this.ctx = this.canvas.getContext('2d');
2029
this.ctx.fillRect(0,0, 30, 30);
2130
31+
this.turnSpan = $('#turn');
32+
this.playersSpan = $('#players');
33+
this.colors = ['#FF0000', '#0FF000', '#00FF00', '#000FF0'];
34+
this.playerColors = [];
35+
2236
// Setup size
2337
$(this.canvas).prop('width', width);
2438
$(this.canvas).prop('height', height);
2539
$(this.canvas).css('width', width + "px");
2640
$(this.canvas).css('height', height + "px");
2741
}
2842
43+
/**
44+
* View a game
45+
*/
46+
BotBattle.prototype.viewGame = function(gameId) {
47+
this.gameId = gameId;
48+
this.start();
49+
};
50+
51+
/**
52+
* Start polling the current game
53+
*/
2954
BotBattle.prototype.start = function() {
3055
this.running = true;
3156
this.getState();
3257
};
3358
59+
/**
60+
* Stop polling the current game
61+
*/
3462
BotBattle.prototype.stop = function() {
3563
this.running = false;
3664
};
3765
66+
/**
67+
* Get the current game state
68+
*/
3869
BotBattle.prototype.getState = function() {
39-
$.get('api/', $.proxy(this.onState, this));
70+
if (this.stateRequest) {
71+
this.stateRequest.abort();
72+
}
73+
this.stateRequest = $.get('api/games/' + this.gameId, $.proxy(this.onState, this));
4074
};
4175
76+
/**
77+
* Process the recieved game state
78+
*/
4279
BotBattle.prototype.onState = function(data) {
4380
// TODO: Validate data
4481
this.state = data;
82+
var tiles = this.state.board.tiles;
83+
84+
// Setup player colors
85+
this.playerColors = {};
86+
var userTileCount = {};
87+
for (var i=0; i<this.state.players.length; i++) {
88+
this.playerColors[this.state.players[i].id] = this.colors[i];
89+
userTileCount[this.state.players[i].id] = 0;
90+
}
91+
92+
// Get the user tile counts
93+
for (var i=0; i<tiles.length; i++) {
94+
if (tiles[i].player !== null) {
95+
userTileCount[tiles[i].player.id]++;
96+
}
97+
}
98+
99+
// Show the current turn
100+
this.turnSpan.html(this.state.turn);
101+
102+
// List the users, in their color
103+
var players = [];
104+
for (var i=0; i<this.state.players.length; i++) {
105+
var player = this.state.players[i];
106+
players.push('<span style="color:'+this.playerColors[player.id]+'">' + player.user.username + ':' + userTileCount[player.id] + '</span>');
107+
}
108+
this.playersSpan.html(players.join(', '));
109+
45110
this.draw();
46111
47112
if (this.running) {
48113
setTimeout($.proxy(this.getState, this), 50);
49114
}
50115
};
51116
117+
/**
118+
* Draw the current game state to the canvas
119+
*/
52120
BotBattle.prototype.draw = function() {
53121
this.ctx.clearRect(0,0, this.width, this.height);
54122
55123
var tiles = this.state.board.tiles;
56-
var tileWidth = this.width / tiles.length;
57-
var tileHeight = this.height / tiles[0].length;
124+
var players = this.state.players;
125+
var tileWidth = this.width / this.state.board.width;
126+
var tileHeight = this.height / this.state.board.width;
58127
59-
for (var x=0; x<tiles.length; x++) {
60-
for (var y=0; y<tiles[x].length; y++) {
61-
if (tiles[x][y].owner == 0) {
62-
this.ctx.fillStyle="#FF0000";
63-
}
64-
else {
65-
this.ctx.fillStyle="#0000FF";
128+
for (var i=0; i<tiles.length; i++) {
129+
if (tiles[i].player !== null) {
130+
this.ctx.fillStyle=this.playerColors[tiles[i].player.id];
131+
this.ctx.fillRect(tiles[i].x*tileWidth, tiles[i].y*tileHeight, tileWidth, tileHeight);
132+
}
133+
}
134+
135+
for (var i=0; i<players.length; i++) {
136+
var overlaps = 0;
137+
for (var j=0; j<players.length; j++) {
138+
if (i == j) continue;
139+
if (players[i].x == players[j].x && players[i].y == players[j].y) {
140+
overlaps++;
66141
}
67-
this.ctx.fillRect(x*tileWidth, y*tileHeight, tileWidth, tileHeight);
68142
}
143+
144+
var overlapPadding = overlaps > 0 ? (tileWidth-10)/(overlaps+1)*i : 0;
145+
146+
this.ctx.fillStyle=this.playerColors[players[i].id];
147+
this.ctx.fillRect(players[i].x*tileWidth + 5 + overlapPadding, players[i].y*tileHeight + 5, tileWidth - 10 - overlapPadding, tileHeight - 10);
148+
this.ctx.strokeRect(players[i].x*tileWidth + 5 + overlapPadding, players[i].y*tileHeight + 5, tileWidth - 10 - overlapPadding, tileHeight - 10);
69149
}
70150
71151
// Draw the grid
72-
for (var x=0; x<tiles.length; x++) {
73-
for (var y=0; y<tiles[x].length; y++) {
74-
this.ctx.strokeRect(x*tileWidth, y*tileHeight, tileWidth, tileHeight);
75-
}
152+
for (var i=0; i<tiles.length; i++) {
153+
this.ctx.strokeRect(tiles[i].x*tileWidth, tiles[i].y*tileHeight, tileWidth, tileHeight);
76154
}
77155
};
78156
@@ -84,6 +162,15 @@ function BotBattle(width, height) {
84162
padding: 0;
85163
86164
background-color: #333;
165+
166+
font-family: sans-serif;
167+
}
168+
169+
#controls {
170+
width: 800px;
171+
margin: 20px auto;
172+
173+
color: #fff;
87174
}
88175
89176
#canv {
@@ -95,6 +182,12 @@ function BotBattle(width, height) {
95182
</style>
96183
</head>
97184
<body>
185+
<div id="controls">
186+
Game <input id="gameId" type="text" value="1"/>
187+
<input id="viewGame" type="button" value="View"/>
188+
Turn: <span id="turn">0</span>
189+
Players: <span id="players"></span>
190+
</div>
98191
<canvas id="canv" id="app">
99192

100193
</div>

‎tests/Services/GameServiceTest.php

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
3+
use PHPUnit\Framework\TestCase;
4+
5+
use BotBattle\Config;
6+
use BotBattle\Data\Db;
7+
use BotBattle\Services\UserService;
8+
use BotBattle\Services\GameService;
9+
use BotBattle\Models\Game;
10+
use BotBattle\Models\Move;
11+
12+
class GameServiceTest extends TestCase {
13+
14+
private static $userService;
15+
private static $gameService;
16+
private static $user;
17+
18+
public function setUp() {
19+
$config = new Config(include(__DIR__ . '/../../src/public/api/config.php'));
20+
$db = new Db(
21+
$config->get(Config::DB_HOST),
22+
$config->get(Config::DB_DATABASE),
23+
$config->get(Config::DB_USER),
24+
$config->get(Config::DB_PASS)
25+
);
26+
27+
self::$userService = new UserService($db);
28+
self::$user = self::$userService->addUser('UnitTestUser');
29+
30+
self::$gameService = new GameService($db, self::$userService);
31+
}
32+
33+
public function tearDown() {
34+
self::$userService->deleteUser(self::$user);
35+
}
36+
37+
public function testCreateGame() {
38+
$game = self::$gameService->createGame(0);
39+
40+
$this->assertNotEquals(null, $game);
41+
$this->assertEquals(0, $game->difficulty);
42+
43+
return $game;
44+
}
45+
46+
/**
47+
* @depends testCreateGame
48+
*/
49+
public function testJoinGame($game) {
50+
$user = self::$userService->addUser('UnitTestUser-Join');
51+
$player = self::$gameService->joinGame($game, $user);
52+
53+
$this->assertNotEquals(null, $player);
54+
55+
return $player;
56+
}
57+
58+
/**
59+
* @depends testCreateGame
60+
*/
61+
public function testGetGame($game) {
62+
$game = self::$gameService->getGame($game->getId());
63+
64+
$this->assertNotEquals(null, $game);
65+
}
66+
67+
/**
68+
* @depends testCreateGame
69+
*/
70+
public function testGetBoard($game) {
71+
$board = self::$gameService->getBoard($game->board->getId());
72+
73+
$this->assertNotEquals(null, $board);
74+
}
75+
76+
/**
77+
* @depends testJoinGame
78+
*/
79+
public function testGetPlayer($player) {
80+
$player = self::$gameService->getPlayer($player->getId());
81+
82+
$this->assertNotEquals(null, $player);
83+
84+
return $player;
85+
}
86+
87+
/**
88+
* @depends testCreateGame
89+
* @depends testGetPlayer
90+
* @depends testGetBoard
91+
*/
92+
public function testDeleteGame($game, $player) {
93+
$result = self::$gameService->deleteGame($game);
94+
$this->assertEquals(true, $result);
95+
96+
$result = self::$userService->deleteUser($player->user);
97+
$this->assertEquals(true, $result);
98+
}
99+
100+
public function testPlayGame() {
101+
$game = self::$gameService->createGame(0);
102+
$userA = self::$userService->addUser('UnitTestUser-A');
103+
$userB = self::$userService->addUser('UnitTestUser-B');
104+
$playerA = self::$gameService->joinGame($game, $userA);
105+
$playerB = self::$gameService->joinGame($game, $userB);
106+
107+
$playerA = self::$gameService->getPlayer($playerA->getId());
108+
$playerB = self::$gameService->getPlayer($playerB->getId());
109+
$game = self::$gameService->getGame($game->getId());
110+
111+
$this->assertEquals(Game::STATE_RUNNING, $game->state);
112+
113+
$actions = [
114+
Move::ACTION_NONE => 0,
115+
Move::ACTION_LEFT => 1,
116+
Move::ACTION_RIGHT => 2,
117+
Move::ACTION_UP => 3,
118+
Move::ACTION_DOWN => 4
119+
];
120+
121+
for ($i=0; $i<$game->length; $i++) {
122+
$move = self::$gameService->makeMove($game, $playerA, $actions[array_rand($actions)]);
123+
$move = self::$gameService->makeMove($game, $playerB, $actions[array_rand($actions)]);
124+
125+
$playerA = self::$gameService->getPlayer($playerA->getId());
126+
$playerB = self::$gameService->getPlayer($playerB->getId());
127+
$game = self::$gameService->getGame($game->getId());
128+
}
129+
130+
$this->assertEquals(Game::STATE_DONE, $game->state);
131+
132+
// Delete the game
133+
$result = self::$gameService->deleteGame($game);
134+
$this->assertEquals(true, $result);
135+
136+
// Cleanup the users
137+
$result = self::$userService->deleteUser($userA);
138+
$this->assertEquals(true, $result);
139+
$result = self::$userService->deleteUser($userB);
140+
$this->assertEquals(true, $result);
141+
}
142+
143+
}

‎tests/Services/UserServiceTest.php

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
use PHPUnit\Framework\TestCase;
4+
5+
use BotBattle\Config;
6+
use BotBattle\Data\Db;
7+
use BotBattle\Services\UserService;
8+
9+
class UserServiceTest extends TestCase {
10+
11+
private static $userService;
12+
13+
public function setUp() {
14+
$config = new Config(include(__DIR__ . '/../../src/public/api/config.php'));
15+
$db = new Db(
16+
$config->get(Config::DB_HOST),
17+
$config->get(Config::DB_DATABASE),
18+
$config->get(Config::DB_USER),
19+
$config->get(Config::DB_PASS)
20+
);
21+
22+
self::$userService = new UserService($db);
23+
}
24+
25+
public function tearDown() {
26+
27+
}
28+
29+
public function testAddUser() {
30+
$user = self::$userService->addUser('UnitTestUser');
31+
32+
$this->assertNotEquals(null, $user);
33+
}
34+
35+
/**
36+
* @depends testAddUser
37+
*/
38+
public function testLoginExisting() {
39+
$user = self::$userService->login('UnitTestUser');
40+
41+
$this->assertNotEquals(null, $user);
42+
43+
return $user;
44+
}
45+
46+
/**
47+
* @depends testLoginExisting
48+
*/
49+
public function testDeleteUser($user) {
50+
$result = self::$userService->deleteUser($user);
51+
52+
$this->assertEquals(true, $result);
53+
}
54+
55+
/**
56+
* @depends testDeleteUser
57+
*/
58+
public function testLogin() {
59+
$user = self::$userService->login('UnitTestUser');
60+
61+
$this->assertNotEquals(null, $user);
62+
63+
self::$userService->deleteUser($user);
64+
}
65+
66+
}

‎tests/run_tests.sh

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
cd ../deploy_resources/db
3+
./reload.sh
4+
cd ../../tests
5+
phpunit --bootstrap ../src/vendor/autoload.php .

0 commit comments

Comments
 (0)
Please sign in to comment.