Project 308: Augmented Reality Mario Kart
by
Joseph Abad
David Allender
Joryl Calizo
Ryan Gaspar
Gavin Lee
Senior Project
COMPUTER ENGINEERING DEPARTMENT
ELECTRICAL ENGINEERING DEPARTMENT
California Polytechnic State University
San Luis Obispo
June 2011
Table of Contents
I.
II.
III.
IV.
A.
B.
C.
D.
E.
F.
A.
B.
C.
D.
E.
F.
i.
ii.
iii.
iv.
i.
ii.
iii.
i.
ii.
iii.
iv.
v.
vi.
i.
ii.
iii.
iv.
i.
ii.
iii.
iv.
i.
Acknowledgements
Introduction
Requirements
Game Mechanics
Hardware Mounting
Image Overlay
Speed Regulation
Networking
Positioning
Design
Game Mechanics
Game Objects
Grid
Game Model
Controller
Hardware Mounting
Foundation Options
Laptop Mount Option
Camera and Micro-controller Option
Image Overlay
Design Choices
Image Layers
a.
Camera Layer
b.
HUD Layer
c.
Object Layer
Image Layer Controller
Windows Operating System
VideoMan Library
Playing a Sound
Speed Regulation
Backround Information
Micro-controller Input Signal
Micro-controller Output Signal
Communication
Networking
Background
Game Server
Game Kart(Client)
Packet Structure
a.
Server to First-Person View (Padded with "zero" elements)
b.
Server to Overhead View (NOT padded with "zero" elements)
c.
Overhead View to Server (NOT padded with "zero" elements
d.
Server to RC Controller
e.
Pre-loaded Map Data (command-line parameter)
f.
Server to 2D Map (NOT padded with "zero" elements)
g.
Kart to Server
Positioning
Multilateration
V.
ii.
iii.
iv.
v.
A.
B.
C.
D.
E.
i.
ii.
iii.
iv.
i.
ii.
iii.
iv.
i.
ii.
iii.
iv.
i.
ii.
iii.
iv.
i.
ii.
iii.
iv.
F.
VI.
VII.
i.
ii.
iii.
A.
B.
C.
Build
Triangulation
Global Positioning System (GPS)
Differential-GPS (D-GPS)
Dead Reckoning (DR)
Game Mechanics
Grid Objects
Grid
Game Model
Controller
Hardware Mounting
Equipment and Materials Used
The Foundation
Laptop Mounting
Camera and Micro-Controller Mounting
Image Overlay
OpenGL Setup
Pulling Camera Frames
Adding HUD Items to Camera Frame
Placing 3D Objects into the View
Speed Regulation
readADC()
speedChange(uint8_t aData,uint8_t mode)
setPWM(uint8_t duty)
Revision 2 Speed Regulation Design
Networking
General Information
Game Server
a.
server_setup()
b.
void connect(void)
Kart’s Networking Specifications
a.
kart_setup()
Dual Network Commands
a.
int recv_data(char *buffer)
b.
int send_data(char *buffer)
Positioning
Constructing a Working Positioning System
a.
Ground Nodes for Multilateration and Triangulation
I.
Setting Up the Transmitter and Receiver
b.
GPS Coordinates For Localization
I.
Converting NMEA to Decimal Degrees (DD)
c.
Localization by Odometry
I.
Wheel Encoding
II.
Determining Orientation
Test Plan
DR Testing
Integration and Results
Integration of Dead Reckoning
First Run Test Results
Second Run Test Results
Conclusions and Future Work
A.
B.
D.
E.
F.
Appendices
Appendix A:
Appendix B:
Appendix C:
i.
ii.
iii.
iv.
v.
vi.
vii.
viii.
ix.
x.
xi.
Game Mechanics
Hardware Mounting
Speed Regulation
Networking
Positioning
Schematics
Parts List, Cost, and Time Schedule Allocation
Program Listing
game.cpp
controller.cpp
grid.cpp
gridObject.cpp
kartObject.cpp
networks.cpp
kartHud.cpp
kartNetworks.cpp
kartObjLayer.cpp
kartViewController.cpp
main.cpp (Laptop)
I.
Acknowledgements
We would like to thank Nintendo for coming out with such an awesome video game, as well as Dr. John
Oliver for giving us the opportunity to go through with this project. We would also like to thank Cal Poly
for its learn-by-doing philosophy because without that, none of this would ever be possible.
II.
Introduction
Mario Kart is a popular go-kart racing game developed by Nintendo. The premise of the game is simple:
drive a go-kart along a racetrack and reach the finish line before the other players. What makes this
game unique, however, is the inclusion of weapons, traps, and other projectiles that a player can use to
gain an advantage in the race.
We have taken on the challenge of not only recreating this amazing game, but using the art of
Augmented Reality to fully immerse the player in the full experience. Rather than play the game on a
television screen with a video game controller, the player can physically sit in a moving go-kart, drive
along a track, and allow the virtual game to unfold on a mounted viewing screen. By overlaying HUD
information and other graphics on the screen, the player can feel as though he or she is interacting with
the virtual game.
Because of the scale of this project, the different components of the project are divided amongst five
team members. These categories, in no particular order, are as follows: Game Mechanics, Hardware
Mounting, Image Overlay, Kart Electronics, Networking, Positioning.
III.
Requirements
A.
Game Mechanics
To reproduce the original Mario Kart game as closely as possible, software for a server must be written
that can run in the background and maintain the game state. The “karts” in the game should refresh
their positioning information using data received from the on-kart electronics. When a player speeds up
or turns their go-kart, these actions are reflected by the corresponding kart in the virtual world.
For debugging, the server will have the option of receiving positioning information from the keyboard
rather than a physical kart. This feature allows the game to be tested even if the electronics on one gokart fails. This functionality can be further developed as a secondary input to the game, allowing players
to play the game via a USB controller or other computer peripherals.
One of the unique features of Mario Kart is that in addition to the racing aspect of the game, players
also have access to a variety of items that they can use to gain an advantage. These items include
projectiles that can be fired at an opposing player or static traps that can slow down other karts that
drive over them. Project 308 will include support for a subset of these items, and the game must keep
track of items currently active on the grid.
Finally, the server must relay information about the game state to the laptop and microcontroller on
each kart. The server is responsible for constructing data packets containing this information and using
the networking class functions to transmit the data.
B.
Hardware Mounting
Each go-kart requires hardware to be mounted in such a way that the end-user will be able to drive
the kart’s without any hardware interfering with his or her driving experience. Hardware that must be
mounted includes a laptop, camera, the micro-controllers, and kart electronics.
For debugging purposes, the hardware must be situated in such a way that developers can quickly get
to each part. This means that the each piece of equipment that is placed on the kart must be completely
modular. Modularity in a design means that each piece of equipment is its own entity. While debugging,
each node should be able to taken out by itself.
C.
Image Overlay
Since the karts can not throw actual shells or run over actual game objects, the image overlay is required
to render these game objects to allow the user to visualize the actual game. The driver also needs to
have a sense of what is going on during the race so a heads up display is also required to keep the player
informed of how he is doing in the race. The main requirements of a racer in the Mario Kart game are as
follows:
● Position - Player’s position in the race (1st, 2nd, 3rd, etc.)
● Lap - The current lap the player is in.
● Inventory - What item the player has.
In term’s of the augment reality world, Mario Kart has a set amount of weapons that make it
distinguishable from any other racing game. To give the player’s a Mario Kart type of feel we will
reproduce these items in the augmented reality world for racer’s to visualize. The set of core augmented
reality objects we would like to produce are:
● Shells
● Item Box
● Banana
D.
Speed Regulation
This segment of the project will connect the software-side of the video game to the hardware-side of
it. Whatever happens in the virtual realm of the video game will be physically shown with this speed
regulation. In order to do this, there must be a medium in between the laptop and the kart that will be
able to:
● pick speed regulation data from a network packet
● translate that data to determine the speed of kart
● modify the speed of the kart
Since the kart’s motor is electrical, the “medium” for this design will be a micro-controller. A microcontroller can communicate to a laptop via a serial connection, as well as manipulate analog voltages to
modify the kart’s speed.
The main task is to design a system so that the karts will not go its normal speed, unless called upon.
The kart’s maximum speed should be reserved for the “gold star effect”. The go-kart must run on four
different speeds: 100% (gold star effect), 75%(in-game normal speed), 50%(slow speed), 0%(completely
stopped).
E.
Networking
Communication is a vital part for a large integrated system. The karts will have to be able to send
messages to the main game server as well as receive messages. Once each kart gets localized
information from the various sensors that are attached, it has to send it to the main server. As of Spring
2011, testing the game has been made with only two karts, but the framework can support many more
devices and karts. The user must specify how many players that are going to be participating in the
game, two is the default, and only choice.
Initially, every kart must connect to the server in order for the game to start. Once the game begins,
then the various machines send status packets to each other the specified information about it’s system.
The packets must be sent at a frequency in which there will be minimal latency between each of the
systems. This means that each packet must be sent and received multiple times within one second.
10hz is an ideal rate at which data will be transferred at to maximize the efficiency of the game
framework.
In order for each machine to connect, the server would need to setup the socket communication for
each device. The server then waits on a “connect” method until each device sends an initial packet to
the server.
F.
Positioning
In Mario Kart, go-karts race to the finish around a track littered with obstructions and items that can
be used as weapons to slow down the opponent or gain an advantage such as power-ups and speed
boosts. Since it is impossible for us to physically create these items and weapons, we have taken these
things in a virtual world and requires a known location of what we can physical have in the real world:
go-karts and a track. The approximate position of the specified go-kart, its opponents, and the items
must all be known in order for the game to be properly played.
Local information of the go-karts in real time is key in incorporating and integrating all parts collectively.
However, unlike global positioning systems that have global coverage, local positioning methods will be
used in order to obtain useful data. The kart’s position determines whether or not item boxes have been
obtained, where to place new items, and the position of used items. Several local positioning systems
were considered while designing this project and will be later discussed.
IV.
Design
System Block Diagram
A.
Game Mechanics
i.
Game Objects
The game needs to support a variety of different items; therefore, the obvious place to start is with a
base class for a simple grid object. This class contains the features common to every object on the grid:
●
●
●
●
●
x and y-coordinates (in 0.1m units)
size / radius of object (for handling collision detection, in 0.1m units)
strength of object (for resolving collisions, unitless value)
ID (unique integer value for each grid object)
object type (enumerated data type representing object)
Because numerous grid objects have mobile properties, a mobile grid object class with the base grid
object class as its parent. Aside from the class members listed above, mobile objects also have the
following information:
●
speed (in 0.1m/s units)
●
heading (in 1° units)
All objects are either a descendant of the mobile object class or extend the base grid object class
directly. The only exception is the shell object class, which can be further divided into green and red
shells, each of which function differently.
The following is a list of all the grid objects supported in the game:
●
●
●
●
●
●
Wall (immobile object, represents outer boundaries)
Green shell (basic projectile, bounces off of walls)
Red shell (projectile with targeting capabilities; tracks nearest opponent)
Banana (trap, slows down kart when hit)
Item box (immobile object, gives kart new item when hit)
Kart (mobile object, can carry an item)
As mentioned above, a new enumerated data type is used to represent the different objects used in the
game, such as karts, weapons, item boxes, etc. As the game is further developed, items can be added
or removed from this list without affecting existing code. In addition to the grid objects above, the
following items are also included:
●
●
●
ii.
Mushroom (temporarily increases speed of kart)
Star (in addition to speed boost, temporarily renders kart invincible to traps and projectiles)
Red/Green Shell x3 (Creates protective barrier around kart, can be launched as projectiles)
Grid
To represent each grid object’s position in relation to the next, a grid keeps track of every active object
using its x and y-coordinate. This grid class represents the main data structure for adding, removing,
and accessing each of the grid objects in the game. Any instance of the base grid object class or its
descendants can be added to this data structure via a class member function, and by passing a pointer
to an object and a new x and y-coordinate, the object’s position in the grid and the object’s own class
members are changed accordingly.
At minimum, the grid class supports the following functions:
●
●
●
●
●
●
iii.
addObject (adds a grid object to the data structure if x and y-coordinate are not occupied)
removeObject (removes a grid object from data structure if present)
moveObject (moves grid object to new coordinates)
get (get pointer to grid object at specified coordinates)
isOccupied (checks if coordinates are occupied)
getCount (get number of objects in grid)
Game Model
The heart of the game requires software that maintains the game state even without continued input
from the user. This allows mobile objects such as projectiles to update their position at a constant rate
until they are removed from the game due to a collision. The update_mobile function, when called
within a timing-sensitive thread, handles the movement of all mobile grid objects.
As mentioned in the previous paragraph, there is a function called find_collisions that detects when two
grid objects are “too close.” find_collisions compares the two objects using their radii and determines if
the two circles overlap. In the event of a collision, the strength values of each object are compared, and
the object with the lower value is removed from the game. If both strengths are equal, both items are
removed. The following cases are exemptions or extensions to this rule:
●
Wall vs Wall
Two wall objects will always have the same strength value, but neither should be removed.
Optimally, walls should be placed such that they never overlap, but poor map design may cause
this problem, and including this case prevents walls from disappearing prior to the start of the
game.
●
Kart vs Wall
If a go-kart collides with a wall in the virtual world, neither object should be removed from the
grid. This case will occur if the kart travels out of bounds; the kart’s maximum speed should
be decreased as a penalty until the kart returns to the track. Unfortunately, this case may also
occur if the positioning system fails, so care must be taken to ensure that the hardware always
returns valid coordinates.
●
Kart vs Kart
If two karts are determined to be within collision distance from each other, chances are the
karts have already collided in the real world, in which case the karts will naturally stop moving.
Regardless, the karts should not be removed from play, as this case may also occur due to a
positioning error.
●
Kart vs Projectile or Trap
Because the strength value of a kart is higher than that of any weapon, the item will always be
removed from the game. However, some adverse effects will happen to the kart as a penalty
for colliding with the item. The kart’s max speed will temporarily decrease; a thread must
be spawned that changes the speed for a few seconds, returning the value to normal before
exiting.
●
Kart vs Item Box
Similar to the previous case, an item box will always be removed when a kart collides with it. In
this case, however, the kart is rewarded with an item if the kart is currently unarmed.
The function add_object places a new object on the grid. After checking to ensure that the given
coordinates are unoccupied, an instance of the correct grid object is created and added to the grid data
structure. When the new object is a projectile, the object’s heading is automatically set to target the
nearest kart. When a player’s kart is armed with an item and wants to use it, the use_item function is
called. use_item is very similar to add_object, but when the algorithm selects a target for projectiles, the
player’s kart is exempt.
When a kart collides with an item box, the game arms that kart with an item if one is not already
available. Normally, a random item is selected from the list of items currently supported, but flags can
be used to prevent some items from being chosen. This feature can prevent a player too far in the lead
from receiving too many speed boosts; likewise a player in last place will not receive traps since no
opponents are behind him or her.
The game class keeps track of other important information, such as each player’s lap count, position in
the race, and max allowed speed. Functions are available to change these values, and the function for
changing max speed can be called in the context of a thread to make speed changes temporary.
Before the game begins, each kart’s lap count is at zero. When the start_game function is called, all
lap counts are set to one simultaneously. This change will be reflected in the packets sent to the kart
laptops; until the game starts, the motors can be forced into braking mode.
iv.
Controller
The game model maintains the game state internally, but another system must be responsible for
passing it information from the players or from keyboard inputs by a user at the server. The controller
bridges the connection between the game and the user(s). The controller receives information about
the game state and passes the relevant information to each of the laptops.
The controller is used to initialize the game by querying the user for starting settings (e.g., number of
players, grid size, etc.). Once all devices are connected to the network, the controller sends the game
the requested settings and calls the game’s start_game function.
The network objects allow the computers mounted on the karts to connect to the main server, and
the controller keeps track of each of these connections. When the electronics on the kart send fresh
positioning data, the controller determines if the data is valid and passes it on to the game to change
the kart’s position in the virtual world. Aside from the positioning data, the kart computer also sends
commands to use an item if the player has one available.
The controller knows the location of each kart in relation to the other objects on the grid. Using
trigonometry and proper thresholding, the controller can calculate the coordinates of each object
relative to the kart’s own coordinate system. The packet sent to the laptop combines this data with the
other information displayed on the HUD for the image overlay software to decode.
The controller provides a user interface for manipulating the game state and printing information to
stdout. The following commands can be selected via keyboard:
●
Display list of grid objects
The controller displays positioning information about each active object on the grid. For karts,
extra information such as the current item and lap count are also included.
●
Display occupancy grid
The controller prints a text-based representation of a 2D overhead view of the game.
●
Add objects
The user can manually add an object to the grid. The controller calls the add_object function of
the game class, which automatically provides projectile items with a target.
●
Change kart places
This command increases or decreases the position of a player, shifting the places of all other
players accordingly. Because grids and tracks are not pre-defined, the game has no way of
automatically determining which kart is in each position.
●
Increment lap count
Until hardware or software is in place to detect when a kart crosses the starting line, a user at
the server needs to manually press a key when a kart has moved on to the next lap.
In case the hardware fails on one of the karts, the controller can reroute positioning information to be
received from the keyboard. The user can control the movement of an in-game kart or use an item with
key presses, and the communication with the game class remains identical.
B.
Hardware Mounting
The main requirement to meet is to have a mounting system which can house all electronics including
the laptop. The proposed design is to have a all electronics in the front. This will make soldering much
simpler because there will less wires to connect together. Also, the mounting must be secure so none of
the parts fly off of the kart while drivers are in the game.
Steel and aluminum will be used because they are both very sturdy metals. There are currently no
laptop mounts for go-kart applications so a laptop mount must be manufactured. A foundation for the
laptop mount must also be manufactured.
Originally, the micro-controller and cameras was to be mounted on top of the laptop mount, but it
was not an elegant design. The final proposed design was to have the camera and micro-controllers
underneath the laptop mount. This will hide most of the 18 gauge wire that will be running from microcontroller to micro-controller.
i.
Foundation Options
The original design for this project was to weld metal tubes onto a cylindrical hole that was already
connect to the front of the kart. Unfortunately nobody in the group had welding experience or a red
card from the Cal Poly machine shop. Welding the metal tubes would also mean the laptop mount
would also need to be welded on as well.
Another possibility was to have a stand alone clamp that would clamp onto the base of the steering
shaft. This would theoretically work, but it would not be a sturdy solution. The sturdiest solution would
to have the laptop mount sit on three points.
The proposed design is to place two threaded rods in the two cylindrical holes. These threaded rods will
then be bolted down, and a screw will be drilled into the rod and the whole. This screw will safely secure
the rod from moving up and down. the bolts will secure the rod from moving from side to side.
ii.
Laptop Mount Option
The proposed laptop mount will be made out of aluminum sheet metal. Sheet metal is easy to bend and
cut. The metal will also be bent on three corners. Bending the corners will also make the design sturdier
compared to a flat piece of sheet metal.
iii.
Camera and Micro-controller Option
Since the camera and micro-controller must be underneath the laptop mount, a new mount must be
implemented for these two components. The proposed idea is to mount two metal brackets underneath
the laptop mount. These two brackets will not be readily available at local hardware stores so the
brackets will have to be custom made at a machine shop. The brackets will be screwed onto the laptop
mount. Also, rubber must be applied at the bottom of these mounts to be prevent ground issues.
C.
Image Overlay
i.
Design Choices
Displaying AR World
Operating System
Programming
Language
Options
Strengths
Weaknesses
Computer Monitor
attached to laptop
-Easy to Mount
-Plenty of control on
how to place the view
-Needs to be powered
-Puts more weight on
the kart
Laptop Monitor
-Self Powered
-Support for keyboard
inputs
-Lightweight
-Harder to mount
Windows
-Widely used operating
system
-multiple support for
graphics drivers
-multiple support for
audio drivers
-Not much experience
on programming on
this Operating system
Mac OSX
-Support for POSIX
library
-All members have
experience on
programming with it
-Difficult to find driver
support
Java
-Multiple libraries
available
-Class based
programming
-Harder to integrate
with other members
who are programming
in C++
-Not much experience
on programming with it
C/C++
-Multiple libraries
available
-Easily integrated with
other member’s code
-Plenty of experience
working in C
-Not much experience
on creating class based
programming in C++
-Class based
programming
Graphics Library
Pulling Camera Frames
Playing Sound
Handling Multiple
Events
Objective C
-Class based
programming
-Multiple libraries
available
-Superset of C
-Little experience on it
Perl/Python
-Able to implement
classes quickly
-Little experience on it
OpenGL
-Plenty of experience
on it
-opensource
Direct X
-Widely used graphics
library
-No experience on it
VideoMan Library
-Works easily with
opengl
-No experience on it
-Extra features not
needed
OpenCV
-Support for image
processing
-Hard to integrate with
opengl
Simple and Fast Media
Library
-Supported on OSX
-Easy to load wav files
-Easy to use
-Easy control over
sound files
-No experience on it
-No support for playing
mp3 files
OpenAL
-Works perfectly with
OpenGl
-Not much support on
OSX
AFPlay funcion on OSX
-Easy to use
-Needs to start a new
process
-No control over it
Spawning Multiple
Threads or Processes
-Easy to implement
-Not have to worry
about blocking calls
-Could get messy
-Hard to track multiple
threads and processes
Have one process
only and make
sure everything is
performed sequentially
-Easy to maintain
-Cleaner than spawning
multiple threads
-Make sure no
functions are blocking
-Harder to implement
ii.
Image Layers
The image overlay can be broken down into three layers where each layer represents a seperate
visualization of the game. Using this layer system avoids the need for threads because it will require
each layer to use a step function that will be called after that layer is setup. The first layer is the actual
camera stream of what the kart is looking at while the race is going on. On top of the camera stream
layer is the layer for the heads up display. This layer shows the current player’s race information. The
third layer is reponsible for rendering the objects that should be seen in the Mario Kart world. This
image layer design was chosen so that a layer can be easily modified without touching the other two
layers. For example, if a different style of a hud is preferred than only HUD layer needs to be changed
and the other layers would still be able to work.
With the image layer design we can also have a running program even if one of the layers stop working.
a.
Camera Layer
This layer’s main responsibilty is to pull frames from the camera that will be displayed on the
screen. The camera layer class will need to pull frames continously to resemeble a real time
video feed. To make this class abstract we will just implement a step function that will have to
be called continously in the higher level classes or else the video will be choppy. The pseudo
code for this function will be as follows:
- void step()
{
if(camera is available)
{
frame = (pull frame from camera);
}
function(display camera frame on screen);
}
b.
HUD Layer
Figure C.i.b
The hud layer is designed so that it closely resemblies the hud of the actual Mario Kart
game. As you can see from figure C.i.b the hud will need to show the player’s position(1),
current weapon(2), race time(3), and lap(4). All of this information is received from the
game mechanics side through the network implentation. The hud items on 5, 6, and 7 can be
implemented in future versions of the game (see future implementations section). The hud layer
will also have a step function that should be called continuously along with the step function of
the camera layer.
c.
Object Layer
The object layer is to provide support for the different objects that will be seen in our 3D Mario
Kart. These items are shells, banana peels, and item boxes. It will be the game mechanic’s
responsibility to notify the program where each object is located relative to the kart’s current
position. To match the design of the two other layers, this class will also have a step function
that should be called along with the other layers.
iii.
Image Layer Controller
This image layer controller is responsible for coordinating the layers together along with the packets
received from the network. This design implementation is modeled from Apple’s Model-View-Controller
design implementation (see developer.apple.com). Most of the work will be done in this class to ensure
that all the other classes are properly coordinated with each other. This class will also have a step
function that will be called continuously in the main opengl loop.
One important function this class will have is a timer function. This timer function will return the current
runtime of the program. With this timer function it will be very easy to measure time intervals. The code
for calculating the frames per second of the graphics would be as follows:
static int lastTFrame = (get current runtime of program);
static int frameCounter = 0;
if((get current runtime of program - lastTFrame >= 1000)
{
fps = frameCounter;
frameCounter = 0;
lastTFrame = (get current runtime of program);
//cout << fps << endl;
}else
{
frameCounter++;
}
The ability of solving time intervals allows us to coordinate the changes of the heads up display and the
augmented reality objects with the real world’s real time.
iv.
Windows Operating System
Even though the other members chose the OSX as their operating system, the image overlay
was
first built and tried on Windows. The decision to go with this was mainly because of the past experience
of using opengl on Windows. There was also plenty of support and tutorials with using opengl and a
couple of sound libraries on Windows. When it came down to integrating with the networks section of
the project the networking could not work because the member doing the networks did not have any
experience on networking with Windows. In order to compensate for this, the image overlay system was
eventually ported to OSX, with some adjustments, to make it easier on the member that is doing the
networking for the whole system.
v.
VideoMan Library
The second main reason Windows was chosen in the first place was because the VideoMan library
worked perfectly with Windows. The VideoMan library was made especially for augmented reality
and computer vision programs. VideoMan pulled frames from the camera constantly while having an
OpenGL layered on top of it for graphics. When it came to porting the whole image overlay system to
a unix operating system, VideoMan would no longer work because there is currently no driver support
for a unix operating system with the library. The VideoMan library was discarded to fix this problem and
instead a function was created that would continuously pull frames from the camera with OpenCV. For
more information on VideoMan refer to the bibliography section of this report.
vi.
Playing a Sound
Trying to find a sound library that would work with a unix based machine was a little bit hard due to the
lack of support for drivers. OpenAl was first method to be tried because XCode came prebuilt with the
OpenAL library and OpenAL was said to work perfectly with OpenGL. The problem that we ran into with
this design is that OSX no longer supported the ALUT library which was needed to load and play sound
files. There was a way to load sound files into OpenAL but it required a lot of work that would require
on decoding sound files. The next method that was tried was to just call seperate process and make a
process call to OSX’s afplay command. This worked out pretty good except for the fact that we would
run into multiple forks when dealing with multiple sounds. Finally a sound library was found that worked
perfectly with OSX and that was the Simple and Fast Multimedia Library. An example of playing a sound
file is the following:
sf::SoundBuffer itemreel.LoadFromFile("./sounds/itemreel.wav");
sf::Sound scrollWepSound.SetBuffer(itemreel);
scrollWepSound.Play();
D.
Speed Regulation
i.
Backround Information
The Honda Minimoto go kart is an electrical go-kart specifically designed for children. Maximum speed
for these karts is around 12mph based on wheel encoder calculations. There are three main sections
for the kart, which include: Throttle, Minimoto Controller, and a DC Motor. As the throttle is pressed
voltage rises and this voltage gets passed into the Minimoto Controller. The controller then converts
this analog voltage and spins the DC motor. An important concept to note is that as the voltage from
the throttle increases, the go-kart will accelerate; vice versa, as voltage decreases the go-kart will
decelerate.
The voltage rail for this kart from the throttle to the minimoto controller is around 4.5 V.
ii.
Micro-controller Input Signal
The throttle acts like a potentiometer. Increasing resistance will increase voltage. This is exactly what
happens when stepping on the throttle. As the throttle gets pressed, voltage increases. However, a
micro controller can only read a digital value (either 5V or 0V). In order to read this throttle value, there
must be an Analog to Digital conversion. After conversion, the throttle will be read as a value from 0255. This 8-bit number is used to determine the speed of the kart.
iii.
Micro-controller Output Signal
The output of the micro controller must be an analog voltage. A micro controller is only able to output
a TTL low or high. In order to output an analog voltage, the MCU must use a digital potentiometer or a
pulse-width modulated signal.
A digital Potentiometer will be able to take in a digital number and translate this digital number to an
analog voltage. A pulse-width modulated signal is essentially a duty-cycle varied digital voltage.
Either adjusting the value of the digital potentiometer or the duty cycle will be two viable options for
speed regulation.
iv.
Communication
Communication with the computer and micro-controller, in general, can be made via a communication
interface like SPI,I2C, etc. Since the arduino is already used for Positioning, it would make sense to send
speed data from the arduino. For positioning, the compass and the wheel encoder sends its data to a
serial monitor. This serial monitor displays the speed as well as compass data. The Serial monitor, for
speed regulation, will be used to send data to the AVR chip. Pushing specific keyboard keys will limit the
speed of the kart.
Since we will be using the Arduino for communication purposes, this data must be sent over to the AVR.
In order to do this, two data bits from the arduino will be sent to the AVR as inputs. Two bits are only
necessary because there are only four different speeds to choose from.
E.
Networking
Figure X: Network Diagram
i.
Background
In order for each device to communicate with each other, there has to be a certain protocol used.
Initially the communication protocol that was considered to be used was the ZigBee Specification,
which uses the IEEE 802.15.4 standard. This standard specifies the physical layer, as well as media
access control for low-rate wireless personal area networks. This entails that the protocol makes data
communication seamless to transfer from device to device. Neither side, client nor server, would have
to worry about addressing the individual layers in order to receive and decode the packets. We would
just need a ZigBee device on the kart, as well as one on the main server. This would have been ideal for
the initial designs, but since there were going to be computers mounted on the karts, it was decided
that using a ZigBee device would just add another component to the system.
This protocol is used by the xBee 1mW chip antenna. It has a 2.4 gHz transmit frequency, but is
bottlenecked by the uart connection on the chip. All that needs to be connected to the xBee is a 3.3 V
power line, GND, and the DOUT and DIN pins for the UART connection.
Finally, a consensus was reached after some amount of research that each member of the team did.
The xBee idea was scrapped, only to be replaced by the IEEE 802.11G standard. This lessened the
complexity of the project, because it took out another hardware to software component that needed to
be integrated.
It was not in the initial design to include a fully functional computer on the go-karts due to the power
issue, as well as the bulkiness of placing an actual computer on the kart, even if it were to be a netbook.
After further research and meetings, a consensus was made to include a fully functional computer. This
allows the networking part of the project to work off of the regular computer’s wireless card, and use
the IEEE 802.11G standard. Luckily, the computers that were used were Apple’s Macbook Pro line of
laptops, so there was plenty of power as well as battery life for our implementation.
Once the wireless communication protocol was finalized, it was necessary to find a fitting design
method to be used for the actual communication. UDP multicast was the initial implementation for
that. After much research, it was apparent that much more work would need to be put in to broadcast a
packet on all the open ports, as well as consume much more network traffic than necessary.
For the final implementation of this project, unicast UDP communcation was used. This way each device
connects to the main server, and the server only sends messages to the devices that need to be told.
One example would be if one physical kart connected, and one ghost kart connected, then it would just
send packets to the physical kart instead of the ghost kart. This is a practical method because it not only
reduces network traffic, but also decreases processing power of the server.
Our network infrastructure can support as many players as possible playing, but the game mechanics
framework only supports 2 players at the moment. This can easily be changed in the code to handle
more players.
ii.
Game Server
Since the game uses a client and server communication model, there needs to be specific
responsibilities for the server. In the initial stages of development, the code was not modular, so it was
almost like running a C program. Very procedural and not as efficent and easy to use as if it were ported
to C++. After modularizing the code, a networking class was created. The user first has to initialize
a new networking object by passing in what type of object that is trying to initialize, as well as what
player number it is connecting to. In this case the server waits for the client, or kart’s connection. Once
the kart sends an initial packet to the server, a connection has been made. This way there is seamless
communication on the port number to where the kart has been initialized. Once all karts have been
initialized, the game mechanics know that the game should start, and the game startup sequence
begins.
The information that the packet contains varies from device to device. Because no image processing
is done on the kart side due to its computational requirements, the server sends each kart information
about what it should be overlaying. The kart then processes this information and places object images
onto the screen in as described in the packet structures below. There is also other information that gets
piggy-backed on each packet. One example of another packet item would be the max speed that the
kart is allowed to run at.
iii.
Game Kart(Client)
In the kart’s networking side, the user creates a new networking object, just as it does for the Server
type, but it’s type now is Kart. The one caviat with this design is that the user must specify in code
what the server’s IP address is. This needs to happen because the user cannot just communicate with
any other device, it must be the specific to the server to ensure unicast communication. Also in the
initialization, the user must specify a player number. The port on which the connection is being made
on is dependent on the player number. This ensures no two karts connect on the same port.
The initial connection packet that the kart sends is very arbitrary and doesn’t need to contain any
specific data. The main purpose that the initial packet is sent, is to let the server know the return
information to where the return packet should be sent. The kart also sends various information to the
server, such as it’s own velocity, heading, and whether or not an item has been used. The karts sends
this information to the server as soon as it processes the server information that gets received.
iv.
Packet Structure
The packet structure for each respective device is as designed below:
a.
Server to First-Person View (Padded with "zero" elements)
Format: H1,H2,H3,H4,H5,H6,H7,OC,O1T,O1x,O1y,O2T,O2x,O2y, ...
(HUD info)
H1: (uint8_t) Kart No. (1 or 2)
H2: (item_values_t) Current item (see items.h)
H3: (item_values_t) Current shield (STAR, REDSHELL, GREENSHELL, or NO_ITEM)
H4: (item_values_t) Shield count (0,1,2,3, or STAR)
H5: (uint8_t) Lap No. (0 to 3)
H6: (uint8_t) Place (1 or 2)
H7: (uint8_t) Max speed (for speed regulation, 0 to 100%)
(Object info)
OC: Unused
O1T: (item_values_t) Object 1 type
O1x: (uint8_t) x-coordinate (0.1m units)
O1y: (uint8_t) y-coordinate (0.1m units)
O2T: (item_values_t) Object 2 type
O2x: (uint8_t) x-coordinate
O2y: (uint8_t) y-coordinate
...
----
b.
Server to Overhead View (NOT padded with "zero" elements)
Format: H,K1i,K2i,F,U,O1T,O1x,O1y,O2T,O2x,O2y, ...
OR
Format: H,K1i,K2i,F,U,O1C,O1T,O1x,O1y,O2C,O2T,O2x,O2y, ...
Example: "SO,1,3,A,0,1,50,50,3,100,100,6,75,25"
(Header)
H: (char*) "SO"
(HUD info)
K1i: (item_values_t) Kart 1 item
K2i: (item_values_t) Kart 2 item
(Format)
F: Format (determines what type of packet is received)
A: All items in string. Clear list before adding each item.
C: Changes only.
(Unused)
U: Unused
*For format A:
O1T: (item_values_t) Object 1 type
O1x: (uint8_t) x-coordinate (0.1m units)
O1y: (uint8_t) y-coordinate (0.1m units)
O2T: (item_values_t) Object 2 type
O2x: (uint8_t) x-coordinate
O1y: (uint8_t) y-coordinate
*For format C:
O1C: (char) Change (A: Add object, R: Remove object)
O1T: (item_values_t) Object 1 type
O1x: (uint8_t) x-coordinate (0.1m units)
O1y: (uint8_t) y-coordinate (0.1m units)
O2C: (char) Change (A, R)
O2T: (item_values_t) Object 2 type
O2x: (uint8_t) x-coordinate
O1y: (uint8_t) y-coordinate
...
----
c.
Overhead View to Server (NOT padded with "zero" elements
Format: H,N,U,K1n,K1x,K1y,K1h,K1s,K1u, ...
Example: "O,2,0,1,123,102,197,0,1,2,54,67,349,0,0"
(Header)
H: (char) 'O'
(Overview)
N: (unsigned int) Number of karts
(Unused)
U: Unused
(Kart info)
K1n: (unsigned int) Kart No. (1 or 2)
K1x: (unsigned int) x-coordinate
K1y: (unsigned int) y-coordinate
K1h: (unsigned int) heading
K1s: (unsigned int) speed
K1u: (bool) use item
...
____
d.
Server to RC Controller
Format: H,U,K1n,K1s,K2n,K2s, ...
Example: "SRC,0,1,0,2,1"
(Header)
H: (char*) "SRC"
(Unused)
U: Unused
(Kart info)
K1n: (unsigned int) Kart No. (1 or 2)
K1s: (unsigned int) Speed regulation (see config.h)
K2n: (unsigned int) Kart No. (1 or 2)
K2s: (unsigned int) Speed regulation
…
e.
Pre-loaded Map Data (command-line parameter)
Format: H,U,Dc,Dr,OC,O1T,O1x,O1y,O2T,O2x,O2y, ...
(Header)
H: (char) 'M'
(Unused)
U: Unused
(Dimensions)
Dc: (unsigned int) Number of columns
Dr: (unsigned int) Number of rows
(Object info)
OC: (unsigned int) Object count
O1T: (item_values_t) Object 1 type
O1x: (uint8_t) x-coordinate (0.1m units)
O1y: (uint8_t) y-coordinate (0.1m units)
O2T: (item_values_t) Object 2 type
O2x: (uint8_t) x-coordinate
O2y: (uint8_t) y-coordinate
...
f.
Server to 2D Map (NOT padded with "zero" elements)
Format: H,K1i,K2i,Dc,Dr,O1T,O1x,O1y,O2T,O2x,O2y, ...
Example: "S2D,1,3,200,250,1,50,50,3,100,100,6,75,25"
(Header)
H: (char*) "S2D"
(HUD info)
K1i: (item_values_t) Kart 1 item
K2i: (item_values_t) Kart 2 item
(Dimension)
Dc: (unsigned int) Number of columns
Dr: (unsigned int) Number of rows
O1T: (item_values_t) Object 1 type
O1x: (uint8_t) x-coordinate (0.1m units)
O1y: (uint8_t) y-coordinate (0.1m units)
O2T: (item_values_t) Object 2 type
O2x: (uint8_t) x-coordinate
O1y: (uint8_t) y-coordinate
…
g.
Kart to Server
Format: X, Y, V, H, IU
Example: "120, 70, 6.5, 240, 1"
(X position)
X: (unsigned int) X position in relative game coordinates
(Y position)
Y: (unsigned int) Y position in relative game coordinates
(Velocity)
V: (unsigned long) Speed at which kart is traveling at(in m/s)
(Heading)
H: (unsigned int) Compass reading for where kart is heading(0-359)
(Item Used)
IU: (unsigned int) Item used
F.
Positioning
Calculations for localization is done within a microcontroller attached to each go-kart, which then sends
this processed information wirelessly back to the server. The kart’s position determines whether or not
item boxes have been obtained, where to place new items, and the position of used items.
Figure 1. Arduino Uno board with ATmega328 microcontroller
Several methods were considered with all of fall quarter solely dedicated to researching these methods
in our EE 523 class. The following sections give a brief overview of each method we researched.
i.
Multilateration
Multilateration is widely used to accurately locate aircraft. It is very similar to trilateration, with
one small difference in implementation. When using trilateration, transmit and receive times at the
mobile and ground nodes are subtracted to compute the RF time of flight. In multilateration, the clock
on the transmitting node is removed. Rather than determining a location using time-of-flight (ToF)
measurements, time-difference-of-arrival (TDoA) is used instead.
In ToF calculations, distance is used to find locations. This creates intersections between circles or
spheres at a radius of the computed distance. However, TDoA will record the arrival times of the RF
signal. The arrival times found by each ground station is then compared. If they all record the same time,
then the mobile node is an equal distance away from each ground station. Otherwise, the ground nodes
with an earlier arrival time will be closer to the mobile node, and vice versa. In either case, the end
result is that distances are found.
Because multilateration uses TDoA instead of ToF, adding extra ground stations to reduce errors is no
longer a simple drag-and-drop solution, requiring changes to the algorithm for each added station.
ii.
Triangulation
Another technique that can be achieved with RF transceivers is triangulation. However, unlike
trilateration and multilateration, triangulation does not require more than two ground stations and is
not timing critical. Rather, each ground station estimates the direction from which the mobile node’s RF
signal is approaching. By finding the signal’s angle of arrival (AoA) at two separate ground stations, a line
from each station can be extrapolated back to the source of the RF signal. The intersection of these two
lines represents the approximate location of the mobile node.
As seen in Figure 4, the two stationary nodes are located on a common base line parallel to the
horizontal axis. The direction towards the mobile node is represented by an angle from this base line.
Along with this base line, the two traced rays form a triangle, which is where the name of the system is
derived.
Figure 4. Example of Triangulation
As seen in Figure 4, the two stationary nodes (blue) are located on a common case line parallel to the
horizontal axis. The direction towards the mobile node (red) is represented by an angle from this base
line. Along with this base line, the two traced rays form a triangle, which is where the name of the
system is derived.
iii.
Global Positioning System (GPS)
Similar to multilateration, GPS locates a mobile node using multiple stationary nodes with known
locations. However, rather than using ground stations, GPS employs a number of satellites orbiting
the planet. With its twenty-four strategically placed satellites, any location on the planet theoretically
has a direct line-of-sight to at least four of these stations. Thus, a 3-D coordinate can be assigned
to any location on Earth. Instead of receiving the positioning signals, the satellites are configured to
transmit them instead. The GPS receivers on our cellular devices and vehicles then receive these signals
periodically and use them to determine latitude, longitude, altitude, heading, and other useful data.
Because the ground receivers do not actually send their own signals, an infinite number of receivers can
be used without interfering with each other.
Unfortunately, GPS systems suffer from multiple sources of error. Since the satellites are located in
space, they slowly drift over time from their established orbits; although they are programmed to
periodically correct their position, this still causes a slight problem. A more severe problem is clock error
both on the ground receiver and satellite ends. Commercially available receivers typically suffer from
inadequate clocks (like in trilateration, clocks with GHz precision are expensive). On the other hand, the
atomic clocks on each satellite suffer from small clock drifts, which cannot be corrected instantly.
iv.
Differential-GPS (D-GPS)
Differential-GPS employs a stationary GPS receiver to determine the error timing errors and transmit
correction signals to mobile receivers. Unfortunately, in order for D-GPS to work sufficiently, the object
must be close by. Therefore, to cover large geographical areas, multiple ground stations must be placed
for full coverage.
The ground node must know first know its exact position to determine timing errors. Normally, the time
of flight and the velocity of an RF signal (speed of light) are used in the equation
to determine a GPS receiver’s distance from a satellite. With information from at least four satellites,
the receiver’s location can be found using trilateration. However, if the precise location of the receiver
). The result is the
is already known, the equation can be used backwards (
expected time of flight, and can be subtracted from the measured time of flight to determine the timing
error for each satellite transmission. By incorporating error correction into a mobile GPS receiver’s
calculations, errors are reduced substantially.
v.
Dead Reckoning (DR)
Dead reckoning is a method that estimates the present position of an object by using its previous
positions. While more advanced methods such as GPS tracking and triangulation have replaced DR, it
is still used in aircraft and nautical navigation, autonomous navigation, and robotic positioning. The
reason for this is because various elements of the object can be easily found: acceleration, orientation,
or velocity. However, by solely relying on past positions to determine its present position, DR can only
provide approximations. Also, error in past positions create accumulating problems for future positions.
These errors can be due to sudden stops or turns, a miscue in calculating velocity or acceleration, or
incorrect orientation values. Therefore, DR calls for precise measurements or frequent recalibration.
The advantage of DR is that unlike the other methods discussed, it does not require GPS satellites or
ground stations. All is calculated on through the object and does not need further complex math. By
eliminating the need for wireless communication and line-of-sight techniques, DR is an excellent indoor
system solution and does not need expensive materials in order to work properly.
To better understand the process of constructing our final positioning system, Table 1 shows all of their
advantages and disadvantages
System
Indoor?
Advantages
Disadvantages
Multilateration
Triangulation
Yes
Yes
Less clocks to synchronize
Does not need to compute
time taken from object to
ground node
Extra ground stations are
harder to incorporate
Requires line-of-sight
Only two ground stations
needed
Basic calculations
Moving parts introduces
mechanical breakdowns
Signal strength
measurements
very susceptible to
interference
Easy to access positioning
data
Complex data harder to
compute
Error can be as high as
15m
GPS
No
D-GPS
No
Most GPS transceivers are
D-GPS ready
Reduces GPS error
Mobile receivers must be
near base stations
Yes
Does not require base
stations
Readily available parts
Simple calculations
Incorrect data leads to
accumulating error
Requires precise
measurements
DR
Table 1. Lists pros and cons of all systems
V.
Build
A.
Game Mechanics
i.
Grid Objects
As explained in the Design section, each type of grid object has its own individual class. After some
reevaluation, only the major objects such as karts need their own class; while traps may affect players in
different ways, this is handled by the game model and is independent of their class representation. The
class diagram below is representative of the final structure for the grid objects.
ii.
Grid
Originally, the grid was represented as a 2D array of pointers to grid objects (or NULL if a coordinate
is unoccupied. However, this approach proved unnecessary, as a 1D array would suffice. The x and ycoordinate simply mapped to a single index using the equation below:
index = y*NUM_COLS + x
0 < x < NUM_COLS, 0 < y < NUM_ROWS
Similar to a hash function, multiple combinations of x and y values can yield the same index, but by
limiting these inputs to fall within the ranges above, every valid pair of x and y-coordinates yields a
unique index.
Along with the static 1D pointer array, the grid class also uses another data structure in parallel. A linked
list of pointers contains the addresses of each object in the grid. This linked list allows quicker traversal
through the grid objects since the 1D array is much larger and contains several NULL pointers.
In conclusion, the 1D array may have been unnecessary, as everything can be accomplished using the
linked list alone. Additionally, using two parallel data structures requires extra care when an object is
removed, as it must be removed from both structures while only de-allocating memory once.
iii.
Game Model
The game model manipulates the grid data structure by moving objects to new coordinates and adding
or removing objects when necessary. Because every grid object has a method to determine its distance
from any other object, the model can easily detect collisions by comparing these distances to a preset
threshold. As explained in the Design section, each grid object has a preset strength value, and these
different values inherently create a hierarchy when dealing with collisions. However, because there
are very few “special” cases, if-else statements are used instead. The object strength parameter, while
unused, remains present in case they become useful in future revisions.
Other features discussed in the Design section are not available in the current implementation. For
example, there is currently no way to limit which items a kart can receive from an item box. This feature
is not included because without a way to automatically detect the place or lap number of either kart,
such an unfair advantage should not be given to either player.
The move_mobile function runs into a few problems depending on how much time has elapsed since its
last call; since the coordinate system and compass headings are both stored as integers, the calculations
may be rounded such that a shell traveling only a few degrees off of the horizontal axis may in fact
appear to travel directly along that axis indefinitely.
iv.
Controller
Because the grid data structure is stored in the game model, it generates all the necessary data packets
directly and passes them along to the controller. The controller is responsible for creating the socket
connections via the networking class and sending the correct packets to each device.
The most important functionality added to the controller is a graphical user interface. While the original
server required the user to manually type in coordinates for each object to be added, the new GUI
displays all objects on a properly sized window, and objects can be added by clicking on the desired grid
location. The GUI also provides a feature to change the status of an individual kart (speed, lap number,
place, current item, etc.).
B.
Hardware Mounting
For mounting, we wanted to be sure that each kart is able to function normally as well as make sure that
each piece of additional hardware did not interfere with driving experience. For safety reasons, we also
wanted to be sure that the driver pays attention to the road at all times. Also, we wanted to have all
parts safely and securely fastened onto the kart, this was specifically targeted at the MacBook Pro’s.
i.
1
1
2
40
4
12
2
4
2
12
12
12
1
1
1
1
ii.
Equipment and Materials Used
power drill
hex key
3’x3’ Aluminum Sheet Metal
small fitting screws, nuts, and washers
⅜ “ Threaded Rods
⅜” Nuts , washers, and lock washers
L-brackets
rectangular brackets
small glad-ware boxes
banana-to-banana cables
banana cable binding posts
aligator clips
roll of electrical tape
10”x10” sheet of rubber
epoxy glue
5’ roll of velcro
The Foundation
Since we wanted the drivers to pay attention to the road at all times, the most logical place to put the
laptop is in front of the steering wheel. But before a laptop can be situated, a strong foundation had to
be implemented. This foundation had to be very stable, but also be light enough to not complement the
kart’s speed.
The Honda MiniMoto originally had alot of external plastic coverings at the front bumper and side
fenders. As we stripped the kart down from these plastic pieces there were two metal cyndrilical holes
at the front bumper. Two ⅜ threaded steel poles were inserted into these holes. The inner diameter of
the holes was slightly bigger than the pole’s outer diameter so screws were drilled at the base of the
holes and poles for added security. Nuts and lock washers were also applied to add extra tension to
these poles.
Figure X :The foundation of the mount
iii.
Laptop Mounting
As more plastic coverings were stripped off, there was a small threaded area near the steering wheel
that could fit something as small as a screw. An L- bracket was placed at this hole and this bracket and
the two holes will be able to hold the laptop.
Sheet metal was cut and manufactured at the Industrial Manufacturing Engineering department at Cal
Poly. Kevin Williams, an IME professor, assisted in manufacturing the sheet metal base. The sheet metal
had to be cut, and folded at the edges. Holes had to also be cut, so that the two threaded rods can go
through. Below is a picture of the final design.
Figure x: An overview image of the laptop mount design
Once the sheet metal was manufactured, a nut and washer was screwed onto the rod. We required that
the washer was placed horizontal with the L-bracket. The sheet metal base was then placed onto this
foundation and tightly fastened on with screws, nuts, and washers.
iv.
Camera and Micro-Controller Mounting
Initially, the Camera and micro-controllers were placed at the bottom of the metal sheet via two
rectangular brackets. These metal brackets were custom bent at the edges so that these brackets can be
screwed onto the laptop mount. This design securely fastened the camera and micro-controllers, but
the design was not modular. Each connection from the micro-controller was soldered on and there is
a huge chance of loose of connections. It was also very difficult to take out the AVR chip if there was
speed regulation issues. Also, the design lead to common ground issues. The camera mount worked fine
but the micro-controller mount needed a complete redesign.
Figure X: The camera counted on the camera mount.
For revision 2, a new design was developed for the micro-controller mounting. Both micro-controllers
will now be placed inside glad ware. Glad ware will solve the common ground issue because the
container is made of plastic. Banana safety posts were drilled in to the glad ware. There was a total of 6
connections:
● Two Vcc connections
●
●
●
Two ground connections
One throttle connection
One motor controller connection
These connections will be tied to the corresponding connections from the go-kart via alligator clips.
Too prevent further ground issues, electrical duct tape was wrapped around the metal casing of the
clips. These banana cables were secured to kart via zip ties. Note that there are two vcc and ground
connections because the ATmega is being passed in between the throttle and kart controller from the
MiniMoto. The glad ware was then velcro-ed onto the plastic side fender of the go-kart. The figures
below shows the redesign of the micro-controller mount.
Figure X: Revision 2 Micro-Controller mount
Figure X: Revision 2 Wire Connections
C.
Image Overlay
i.
OpenGL Setup
Some initialization of opengl is first required before we can show an opengl window. This intialization
can be seen in main.cpp with a simple function call to InitializeOpenGL(int argc, char **argv). For further
understanding of what this function does refer to the code and read the comments or read the tutorials
on opengl in the bibliography section. The two important functions does set opengl to constant animate
are
glutIdleFunc(glutDisplay);
glutMainLoop();
The first function tells OpenGL that the passed in function should be called everytime opengl is idle
and the second functions tells OpenGL to loop constantly. The display function is passed so that the
animation is running in real time with the race.
ii.
Pulling Camera Frames
Before frames can be pulled from the camera, OpenCV has to initialize the camera by making sure there
is one available. Once the camera is intialized frames can be pulled from the camera to be displayed
onto the screen. The code for initializing and pulling frames from the camera can be seen below.
/*initialize camera*/
CVCaptur *capture = cvCaptureFromCAM(cam);
if(!capture)
{
fprintf(stderr, "Unable to initialize webbcam at %d!\n", cam);
}
/*pull frames*/
IplImage *qFrame = cvQueryFrame(capture);
Since the VideoMan library was discarded we had to find another way of displaying the pulled frames
from the camera as the background in OpenGL. To set the background as the camera stream we placed
a polygon in the background that is textured with the current pulled frame. This can be seen in the
hud.cpp class in the function called display. The polygon is offseted at a far distant to prevent any 3D
animation from colliding.
iii.
Adding HUD Items to Camera Frame
Since OpenCV allows the manipulation of an image’s pixels, we were able to place hud items on the
existing camera frame by focusing on a small area of the frame and changing the pixels with the hud
item’s pixel we wanted in that area. Instead of having a seperate layer for the camera frames, the
hud layer and camera layer were combined into one layer for simplicity. A function call in the class
kartHud.cpp was created to place these hud items onto the current camera frame with some passed in
parameters of where the image should be place. The function call and an example of how to use it can
be seen below:
void kartHud::placeImage(IplImage *background, IplImage *foreground,
double size, double xoffset, double yoffset)
If we wanted to place a red shell hud item on the top left of the current frame the function call would
look like the following:
original = cvLoadImage("./hudImages/redshell.jpg");
placeImage(currFrame, original, 20, 5, 70);
Here is an example of the red shell hud item being placed on the current frame:
iv.
Placing 3D Objects into the View
In order to show some advanced 3D models we first had to find a way to load the vertices of a .obj
file into OpenGL and then with that try to recreate the 3D model of the .obj file. Thankfully there was
a free open source library that enables us to do this. The library classes that enables can be seen in
the appendix section with the names glm.cpp, glm.h, glimg.cpp, and texture.h. With this library I’ve
broken down each 3D object that needs to be display with a draw function that takes in coordinates.
The coordinates passed into this function are relative to the kart’s current position and game world’s
coordinates. In order to draw a red shell that is two units in front of us and one units to left we call the
function drawRShell(-1, 2). The example display of this 3D object can be seen below.
In order to keep track of all the 3D objects that needed to be displayed on the screen an array of
MAXOBJECTS was first initialized in our kartObjLayer.cpp class. This array would hold information of
what type of item needed to be displayed and what the coordinates of that item were. An example
struct of what the array held can be seen below.
typedef struct
{
/* x and y coordinates of an object is relative to the
* objects position. where an increase in y is the increase
* in forward distance of the object and increase in x would
* increase an object to the right of the kart
*/
double x;
double y;
item_values_t type;
}myCoords;
Whenever another class would need to add an object in the view all the class had to do was call the int
addObj(double x, double y, item_values_t type) function. This function would return a key which can be
used to modify the object’s position or value.
D.
Speed Regulation
Before displaying the actual source code for this section, I would like to describe each function used.
The code was written in AVR Studio and the Arduino software. Equipment used in this section include an
Atmel ATmega32 micro-controller and an Arduino UNO. Also, the picture below shows the I/O pinout of
the ATmega32 for revision 2.
Figure X: ATmega32 pinout for revision 2
i.
readADC()
Reads a digital value from the throttle. This value is a value from 0-255.
uint8_t readADC()
{
ADCSRA |= _BV(ADSC); //start AD conversion
while (!(ADCSRA & (1<<ADIF))) {}; //wait for conversion to
complete
return(ADCH);
}
Figure X: readADC() source code
ii.
speedChange(uint8_t aData,uint8_t mode)
Controls the speed of the kart based on the value passed in from the arduino. This function takes in
two parameters: aData is the ADC value which was read from readADC(), and mode corresponds to the
specified speed mode. The function returns a newly modified integer.
Basically, the duty cycle of the kart will be set to a certain threshold for partcular speeds. For example,
if 75% speed is selected, the max possible duty cycle is “129”. Similarly, If 0% speed is selected, the max
possible duty cycle is “0”.
uint8_t speedChange(uint8_t aData, uint8_t mode){
uint8_t newADC;
switch (mode){
case 0:
newADC = aData;
break;
case 1:
if (aData > 129){
newADC = 129;
}
else {
newADC= aData;
}
break;
case 2:
if (aData >128){
newADC = 128;
}
else {
newADC = aData;
}
break;
case 3:
newADC = 0;
break;
default:
newADC = 0;
break;
}
return (newADC);
}
Figure X: speedChange() source code
iii.
setPWM(uint8_t duty)
This function changes the output analog voltage. Remember, adjusting the duty cycle of a pulse-width
modulated signal adjusts the voltage of a signal. The function takes in a duty cycle and will adjust the
PWM signal accordingly.
void setPWM(uint8_t duty){
OCR2= duty;
}
Figure X: setPWM() source code
Depending on the PWM duty cycle returned from speedChange(), this function will output a
relative “analog” voltage.
iv.
Revision 2 Speed Regulation Design
In the second design the wheel encoder code was moved to the ATmega MCU because a control loop
was to be implemented for the kart. However, this closed loop system was not completed by the time of
the demo.
The control system would basically check for speed errors, and regulate the speed based on the analog
signal coming from the throttle. The error gain would adjust the PWM signal until the actual velocity
equals that of the throttle value and/or speed threshold (which was set by the server). For testing
purposes, a seven segment display was used to display the velocity of the kart in meters per second.
/* wheelEncoder()
*calculates the velocity of the wheel
*/
void wheelEncoder(){
float ratio;
newCount = count;
countDiff = newCount-oldCount;
oldCount = newCount;
ratio = ((float)countDiff/(float)TICKS)*(float)CIRCUMFERENCE;
velocity = ratio/TIME;
}
/*transfer()
*Transfers the data to the seven segment Display. The display
*shows the value in this form [XX.XX] where X is an integer.
*/
void transfer(){
int tens;
int ones;
int tenths;
int hundredths;
tens
= ((int)(velocity))/10%10;
ones
= (int)velocity%10;
tenths
= ((int)(velocity*10))%10;
hundredths= ((int)(velocity*100))%10;
SSEG_Write_digit(1,tens);
SSEG_Write_digit(2,ones);
SSEG_Write_digit(3,tenths);
SSEG_Write_digit(4,hundredths);
SSEG_Write_Decimal_Point(SSEG_DP_1);
}
Figure X: Revision 2 wheelEncoder() source code
SSEG_Write_digit(x,y) is a function called from spi_sseg.h, a driver created in CPE 439 for an SPI-based
seven segment display. Note that the Wheel encoder code was the same code used for the IR sensor.
However, TICKS was now defined as 8 because there are only 8 magnets on the wheel of the kart.
E.
Networking
i.
General Information
The networking aspect for the project is fairly seamless, with the user only having to call one connect
function per kart playing.
ii.
Game Server
When the constructor is called with the certain parameters that allow it to create a new server object,
it calls the constructor, which invokes the server_setup() method if the correct parameter was entered.
NETWORK_SERVER is a macro that is just the number 2, that is used as the correct parameter in the
constructor that determines if the server is going to be initialized.
a.
server_setup()
This method calls the necessary commands to help set up the game server. It first calls socket(),
which returns a socket descriptor. The socket descriptor is used as the main channel of information
for how communication is transferred. Because the UDP protocol is being used, SOCK_DGRAM and
IPPROTO_UDP gets passed as the 2nd and 3rd input variable, respectively. Also, it changes the socket
address info’s family to AF_INET to represent the address family for internet sockets. Once that
initialization finishes, then the socket gets binded to the address structure, and server_setup() prints out
the port at which it is waiting for a kart to connect on.
Once the user creates a new networking server object, the connect() method should then be called.
b.
void connect(void)
This method allocates memory for a new packet using the malloc() function. It then waits on
recvfrom() until the specified kart connects to it. Once the kart sends the packet and connects with
the server, it prints a message on the screen that a kart has connected, and then sends back a simple
string “Connected to Server!” to the kart. After that it just frees the packet and exits.
iii.
Kart’s Networking Specifications
When the constructor is called with the certain parameters that allow it to create a new kart object, it
calls the kart_setup() method.
a.
kart_setup()
This method is used when initalizing the network communication for each kart object. This method
is much shorter than the server_setup because it does not bind the port. It calls socket() to return a
socket descriptor. It also uses the object’s own socket address information structure. It addresses the
same AF_INET protocol, and makes sure that the game is playing on the correct port. One variant that it
does that the server does not, is address a specific IP address for the server. This is to ensure that data
is being sent to that direct IP, and not any others.
Once that method is called, the kart must send an initial packet to connect with the server
iv.
Dual Network Commands
a.
int recv_data(char *buffer)
This is the wrapper method to handle seamless receiving of data. It just uses the class information being
the socket and the socket address structures, and receives data on the line. Because it does not use
the MSG_DONTWAIT parameter, it does hangs on receive of data. This means that if no new data gets
transferred, then the game mechanics cannot update. Luckily, since it is running in a separate thread,
the game will not crash due to dependencies on other processes.
The message gets sent into a buffer that must already be allocated. If the buffer is not allocated
beforehand, then bogus data may come in. It also receives MAX_PACKET_SIZE, which is 150 bytes long.
This size is optimal for the implementation because it is not too long that it congests the network, but
not short enough to where data gets shortened due to restrictions.
b.
int send_data(char *buffer)
This is the wrapper method to handle seamless sending of data. It sends the buffer data on the socket
and port that it is respectively connected to. The user must handle all of the buffer sizes, and when
sending, the strlen of the buffer gets passed into the parameter that specifies the length of the data to
send.
F.
Positioning
i.
Constructing a Working Positioning System
a.
Ground Nodes for Multilateration and Triangulation
Initially, multilateration was considered because unlike triangulation, its lack of moving parts made it
easier to construct. We purchased four 434MHz transmitters and receivers in order to create three
ground nodes determining the location of a single kart. Figures 4 and 5 depict these devices.
Figure 5. RF Link 2400bps 434MHz Receiver
Figure 6. RF Link 2400bps 434MHz Transmitter
As an initial test, we connected each device to two different Arduinos and separated them at a
reasonable distance while still being within line-of-sight. Figures 7 and 8 show the setup for each
device.
INSERT FIGURES OF TRANSMITTER AND RECEIVER
I.
Setting Up the Transmitter and Receiver
A sample code found through Spark Fun Electronics was used.
/* * Simple Transmitter Code
* This code simply counts up to 255
* over and over
* (TX out of Arduino is Digital Pin 1)
*/
byte counter;
void setup(){
//2400 baud for the 434 model
Serial.begin(2400);
counter = 0;
}
void loop(){
//send out to transmitter
Serial.print(counter);
counter++;
delay(10);
}
/* * Simple Receiver Code
* (TX out of Arduino is Digital Pin 1)
* (RX into Arduino is Digital Pin 0)
*/
int incomingByte = 0;
void setup(){
//2400 baud for the 434 model
Serial.begin(2400);
}
void loop(){
// read in values, debug to computer
if (Serial.available() > 0) {
incomingByte = Serial.read(); Serial.println(incomingByte,
DEC);
}
incomingByte = 0;
}
As seen above, a simple continuous counter is being sent out through the transmitter and picked up by
the receiver, which does nothing more than read what the transmitter has sent. After running each code
of through the transmitter and receiver, we picked up nothing but noise. Sufficient and helpful could
not be obtained. We assumed that the devices were simply too far apart. To fix this, we put them as
close as possible and added wires as antennas to increase the likelihood of receiving transmitted data.
Unfortunately, this method did not help. Since time was a factor and days were wasted testing and retesting, we decided to scrap both triangulation and multilateration. We could have bought new parts
and tried again but the cost would be more and we may end up with the same problem of receiving only
noise.
b.
GPS Coordinates For Localization
To avoid this, we moved on to the next method of localizing GPS-coordinates into local coordinates for
our outdoor demonstration. The concept seemed doable in a short amount of time and we already had
the components, an Arduino board and the EM-406A GPS Module.
Figure 9. EM-406A Receiver with Antenna from Spark Fun Electronics
INCLUDE DEFINITION/EXAMPLES OF NMEA AND DD
I.
Converting NMEA to Decimal Degrees (DD)
Right away we encountered a problem from the GPS module: it only outputted in NMEA format. NMEA,
or National Marine Electronics Association, format is widely used for real time position information.
In this case, it meant a conversion to decimal degrees in order to find distance in meters. To calculate
distance from two DD points is as follows:
The DD points are expressed in radians and radius equaling the radius of the Earth in meters. This
formula worked fine and always gave accurate results in relative to the coordinates given from the EM406A. Unfortunately it was the module itself that gave us problems. Thorough testing of the module
revealed that it must in wide-open spaces away from any obstruction to avoid bad data and even so,
the data needed constant recalibration in order to stay up-to-date with the several orbiting satellites.
We also could not select the satellites we wanted to use to triangulate coordinates with the EM-406A.
A module with this capability cost hundreds of dollars, not within our budget. Also, even as a localized
coordinate, it could not keep up with the speed of the kart, even when tested at low speeds.
With the issue of the GPS module, it made it impossible for localizing coordinates as well as using D-GPS
unless we spent beyond our budget to obtain better GPS data.
As a last-ditch effort to create a working positioning system with parts we already had, we combined
odometry with compass headings to create a dead reckoning system.
c.
Localization by Odometry
I.
Wheel Encoding
Speed was found using odometry, specifically, wheel encoding. Wheel encoders can provide data for
odometry and will is the simplest way of obtaining localized coordinates for our track and go-karts.
A sensor determines the speed by observing changes specific to that sensor as the wheel of the gokart rotate. For instance, an optical encoder observes the change in light as the wheel spins. This is
accomplished by alternating black and white to maximize and minimize the amount of light the optical
sensor catches. Figure 2 shows an example of the alternating color system attached to the wheel itself.
Figure 2. Black and white color wheel
As the wheel rotates, the sensor reads a series of highs and lows (or 0’s and 1’s) at varying speeds. This
is found by using a counter that will keep track of the changes as the sensor toggles from low to high
or vice versa. After a specified amount of time, for example one second, a calculation from the counter
finds velocity through the simple equation velocity = distance/time. Distance in this case refers to the
circumference of the wheel and a specific number of toggles the sensor reads determines if one full
rotation has been accomplished.
To create thresholds, the LM339 comparator will be used to output either high or low, depending on
what the sensor reads. A sample circuit is shown in Figure 3.
Figure 3. Comparator circuit for encoder
INSERT ACTUAL WHEEL ENCODER CIRCUIT
High and low values for the IR sensor and the eventual hall effect sensor will be read from the wheel as
black and white pictures or evenly placed magnets, respectively. Each change in color or pole represents
a tick. The formula to obtain velocity from number of ticks is as follows:
II.
Determining Orientation
The compass values were found by using its I2C interface on the Arduino Uno.
Figure 8. Compass Module - HMC6352
Heading is important in dead reckoning because it allows us to determine whether an object is going
forward, reverse, or turning left or right. Using Honeywell’s HMC6352 Compass Module eliminates a
lot of design work in actually creating a heading device and allows us to focus on its actual function:
obtaining a correct heading. For best results, it must be mounted in such a way that it remains still. This
will eliminate errors in heading values and reduces the need for recalibration.
To determine local coordinates for the go-karts using speed and heading, the following equations are
used:
ii.
Test Plan
To ensure working functionality of each component of the dead reckoning system, the plan is to test
each part individually.
The sensor must first be able output a high and low value—high equaling the supply voltage and low
equal to ground. With a working comparator, testing the encoder will require a spinning wheel. Having
the go-kart elevated so it does not travel forward as the wheel spins will test that the wheel encoder
can both calculate correct speed values as well as do it in real time. The compass values will be found
by using its I2C interface on the Arduino Uno and can simply be found by shifting the direction of the
compass by hand.
Since both parts do not interact or interfere with each other, integration is simple and the values are
only needed for calculation of the coordinates. Both outputs of the compass and encoder will be sent
into the Arduino Uno where the coordinates are calculated and sent out to the server.
To fully test the positioning system, they must both be connected to the go-kart and the server will
determine if correct positions are found as it traverses through the track.
iii.
DR Testing
Each component was tested individually. The optical sensor sent values through a comparator and was
inputted into the Arduino’s digital pin 4. A counter kept track of the constant toggle between 5V and
0V and vice versa. This was done using the attachInterrupt() function with interrupts being made every
toggle. Also note that 16 toggles equal to one full rotation of the wheel.
/* Event to happen during change from HI - LOW or vice versa */
void encoderEvent()
{
count++;
}
/* Outputs the velocity of the cart in .1 meters per second */
void wheelEncoder()
{
newCount = count;
countDiff = newCount-oldCount;
oldCount = newCount;
float ratio = (float)countDiff/(float)TICKS;
velocity = ratio/(float)TIME*CIRCUMFERENCE;
if(velocity > THRESHOLD) velocity = 2;
velocity = velocity * V_SCALE;
}
An interrupt service routine (ISR) made the velocity calculation every 500 milliseconds. The speed
was determined by how large or small the difference of the counter was every 500 milliseconds. For
example, if the counter difference equals 50 and one full rotation of the wheel is defined by 16 toggles,
then velocity = (50/16)/(0.5 seconds)*circumference of the wheel in meters.
The compass module has a simple I2C interface that output the heading
/* Outputs the heading of the cart in degrees from 0 - 359 */
void compass()
{
Wire.beginTransmission(slave);
Wire.send("A");
Wire.endTransmission();
delay(10);
Wire.requestFrom(slave, 2);
while(Wire.available() && i < 2)
{
data[i] = Wire.receive();
i++;
}
i=0;
value = data[0]*256 + data[1];
}
“slave” represents the default slave address of the compass. At first, I left this as is, 0x42h. However,
incorrect data was being outputted. After some research I found that others using this module had the
slave address right-shifted and initialized to 0x21h. After the shift, I finally got useful headings that could
be used for dead reckoning.
VI.
Integration and Results
A.
Integration of Dead Reckoning
Integration was simple because both components did not interact with each other. The only issue was
making sure that they both performed as expected within the ISR and on one Arduino board. Surely
enough they worked to perfection on the first try. Its output is in format: (localized x-coordinate,
localized y-coordinate, heading, velocity). Appendix A shows the full source code for this positioning
system.
The next issue was attaching the entire system onto the go-kart in preparation for a test run. The
compass was to be placed in the front whereas the wheel encoder, or at least the sensor, must be
getting values from the back wheel. To properly do this, long wires connected the output of the wheel
encoder back to the Arduino, which was placed at the first by the compass.
B.
First Run Test Results
Position was successfully found and sent back to the server but it was not accurate enough to get the
game mechanics to work with it 100% of the time. There may be several reasons for this. First, the
compass may have no been mounted correctly and any bumps or imperfections on the track could
throw it off and need recalibration to fix itself into its proper position. Second, an optical sensor was
used to read velocity. However, on a sunny day, it may not have been reading the correct high and low
values as expected. This meant that velocity was not always accurate and changed the location of the
kart. Once again, the disadvantage of dead reckoning is that errors pile up and worsen the system until
recalibrated. We added several recalibration stations throughout the course in order to increase the
accuracy of the go-kart’s position. Lastly, the radius of the coordinate that the item box or weapon may
have been too small and made it difficult for the go-karts to easily interact with it.
There was very little problem integrating the game mechanics with the image overlay software. As more
objects were added by the server, the imaging handled the increased processing without extra delay.
However, a cap was still added to the amount of objects displayed on the kart monitor at a given time
(to prevent the data packets from growing too large).
A recommendation to upgrade this system is to fix the mounting of the compass to make it more stable
and susceptible to bumps. Another recommendation was to change the optical sensor to a hall effect
sensor. This eliminates the lighting issue and magnet sensors would be much more accurate in detecting
toggles.
C.
Second Run Test Results
A hall effect sensor was implemented and it provided better results than the IR sensor. Maximum
velocity for these karts was calculated and read as 14mph through the serial monitor. Below is the
circuit design of the wheel encoder.
Figure X: Hall effect circuit
The hall effect sensor is placed near a ring of magnets on the wheel. The output of this signal is then fed
into a comparator that replaces the small signal as either a high or low voltage that can read into the
digital pin of the arduino as an external interrupt. When the arduino senses this interrupt, a velocity is
calculated based on the same formula created for the IR wheel encoder. Below is a picture of the final
design of the wheel encoder.
Figure X: Revision 2 Hall-effect Sensor and Magnet Design
Note: The figure does not show the complete wheel encoder system. The metal rectangles on the rim
of the wheel are neodymium magnets. An extra metal bar was attached to the rear bumper of the kart.
The sensor was connected to the wheel encoder circuit which was mounted near the ATmega box. The
sensor was secured using electric tape.
There were alot of hardware issues for the first test run. Alot of the problems included loose wires,
and it was a hassle to desolder and resolder all of the wires together. Soldering took a lot of time so
it was decided to redo the hardware mounting. The new system required that all electronics were to
be encapsulated in plastic cases. The inputs and outputs for the micro-controller were connected via
banana-to-banana wires. This allowed for quicker and easier debugging. See Build: Hardware Mounting.
With the new positioning system in place, the rest of the game ran much smoother. Driving a kart
towards or away from an object appeared more realistic, as the items no longer bounced around due to
positioning jitter.
VII.
Conclusions and Future Work
A.
Game Mechanics
The first change to the game mechanics is a completely redesign of the framework. Many features
originally added were never used (object strength, size, a class for each object, etc.), but were left in the
code in case they later found a use. In addition, the game mechanics will be designed in anticipation for
improvements that may be encountered much later. For example, each object will have 3D coordinates,
and until the vertical axis becomes used, all objects are located on the same plane.
Support will be added for pre-loaded maps. Assuming that information about the playing field is already
known (grid size, location of obstacles), this information can be represented in a text file and passed
to the game during initialization. When the game begins, item boxes will already be created at predetermined locations. With pre-loaded maps, map-based traps and obstacles can also be added.
In the current build, when an item box is picked up or destroyed, it is removed from the game forever.
A new “regenerating” item box will be added that restores itself after being removed for a specified
number of seconds.
The controller will be able to support the new networking changes listed in Future Work: Networking.
Because devices will be allowed to enter at any time, the controller must periodically check the proper
networking objects for changes.
Several interesting additions will be added to the game once the core functionality becomes solid.
Enhanced drafting or slip streaming can be included if the positioning is accurate enough. If the track
layout is known, a simple AI player can also be created that drives around the track interacting with
other karts in the virtual world.
B.
Hardware Mounting
The major addition in the future for this project is to hide all of the electronics. Currently, every piece of
electronic on the kart is placed inside a glad ware. Glad ware will protect the electronics very well, but
hiding the electronics inside the fenders will improve it’s protection. Also, if the electronics are hidden it
will be easier to get in and out of the kart.
Another addition is to include an iPhone mount near the steering wheel. An iPhone app has already
been made for this project, and players will be able to see an overview of the map with an iPhone. This
app can be used as a mini-map so that players will be able to see where their opponents are at all times.
Currently, all game mechanics requires the player to haunch over the steering wheel to either use an
item or calibrate the kart’s position. For safety reasons, it will be a good idea to mount buttons on the
steering wheel.
Steering servos will be a great addition to this project. Instead of the having steering as mechanical
process, the kart can be electronically steered. If the kart is electronically steered a couple of additional
features can be made for the kart’s. One example is a spin effect. If a kart hits a banana the kart will spin
in a circle. This will better emulate the banana effect in the video game.
C.
Image Overlay
The 3D objects displayed on the screen seem to float when they are traveling in our augmented reality
race world. In order to fix this in the future it would be great to implement a way that the program can
detect where the surface of the road is located and anchor the 3D object to the surface. Since OpenCV is
already being used to pull frames, that library can be used to do all of that image processing in detecting
a road’s surface.
D.
Speed Regulation
The whole speed regulation system will be redesigned. Although PWM worked, it had a lot of flaws. It
takes a couple of seconds for the kart to accelerate at 75% speed. Alot of optimization has been done to
try to fix this issue (like changing clock prescaler frequencies), but there was no noticeable difference.
It was concluded that this was due to the kart’s motor controller. So, in the future, we would like to
purchase better hardware. This will include: a new motor, new motor controller, as well as a new gokart. Better hardware will allow for better manipulation of the speed.
Currently, speed regulation is done by thresholding the duty cycle of the PWM signal. A new closed-loop
system will be added. This closed-loop system will check the speed of the kart, calculate its speed error,
and adjust the current speed by that error. This error signal will be calculated based on the throttle’s
A-D value as well as the current regulation that the kart has. Once calulculated, this error will either
increase or decrease the PWM signal until the actual speed equals the velocity calculated from the
wheel encoder.
Adding this closed-loop system will also add a top speed adjustment. In the future, players will be able
to select how much they weight, and this closed-loop system will make adjustments for this weight. This
will allow for an even playing field for players with highly diverse weights.
Lastly, a “NOS” feature will be an interesting feature to add. A relay or solenoid can be directly
connected to the battery and when a button is pressed on the steering wheel, the kart will get a a huge
boost for a couple of seconds, hence, cause a “NOS” boost.
E.
Networking
As of right now, the user must specify how many players that will be participating in this game, and it
only supports two different machines, be it players, “ghost kart’s,” or Apple’s iDevices. In the future,
it is planned to scale this up to as many players as possible to fit within the bounds of the course. It
is also planned to have a dynamic connection, so any user can join in at any time and the game will
accomodate for it. The fact that the users have to connect at the beginning can become a hindrance
in the future in the case that a new player would like to join in the middle of a game, thus, an “infinite”
scale of players would highly benefit this project.
Furthermore, it would be highly convenience if the system had a remote data gathering protocol. This
entails the main game server would get data from an HTTPS server, and also allow anonymous internet
users to check on the status of a game, enabling them to just go to the domain and live view a game.
An HTTPS connection would be necessary to enable security and make sure hackers do not try and
manipulate the game.
F.
Positioning
Although dead reckoning is a simple and effective solution to positioning, a more accurate system is
much needed to improve the overall performance of the game. Therefore, research has been conducted
to create a hybrid system that incorporates fingerprinting based on RSS that will estimate location of the
karts in addition to the dead reckoning system already in use. Fingerprinting can be done indoors but
must be calibrated once at the start since signal strength may change. Hopefully an added positioning
system will reduce error and allow the game to be played more smoothly.
Appendices
Appendix A: Schematics
Appendix B: Parts List, Cost, and Time Schedule Allocation
Part
Cost
Go-Kart(3)
$500.00
36V Batteries(2)
$180.00
36V Battery Charger
$30.00
GPS EM-406(2)
$120.00
Razor IMU
$125.00
Compass Module - HMC6352(2)
$70.00
Arduino Duemilanove(3)
$90.00
Various Mounting Components
$70.00
Creative ZXXX Webcam(2)
$60.00
Total
$1245.00
Appendix C:
Program Listing
NOTE: Due to the very large code base, only the main source code is added in this list. The authors of
this document can be contacted if the complete directory is required.
i.
game.cpp
/** @title game.cpp
* @brief Main file where all of the high-level game mechanics are located.
* @details This file represents the model for a model-view-controller (MVC)
*
architecture. Public methods are called by the controller when changes
*
to the game state need to be made. Other query methods are used by the
*
view to update local game state data displayed on the user interface.
* @author David Allender
* @author Joryl Calizo
* @date March 31, 2011
*/
#include "game.h"
pthread_t temp_speed_thread;
struct speed_thread_param_t {
game *p_game; // pointer to game object
unsigned int kart_num; // kart number (1,2,...)
};
void *reset_speed (void*);
/** This constructor creates a new instance of the game. Objects needed to
* track the game state are properly created.
*
* @param num_cols Number of columns
* @param num_rows Number of rows
* @param num_players Number of players
*/
game::game (uint8_t num_cols, uint8_t num_rows, uint8_t num_players) {
// Save local copies of parameters
this->num_cols = num_cols;
this->num_rows = num_rows;
num_karts = num_players;
// Create array of pointers to kart objects
playerKart = new kartObject*[num_karts];
for (int i=0;i<num_karts;i++) {
// Give invalid values for now
playerKart[i] = new kartObject(0,0,0,NO_ITEM);
}
// Create grid
objectGrid = new grid (num_cols, num_rows);
// Set ready state to false
kart_ready = new bool[num_karts];
for (int i=0;i<num_karts;i++) {
kart_ready[i] = false;
}
// Set lap count
cur_lap = new uint8_t[num_karts];
for (int i=0;i<num_karts;i++) {
cur_lap[i] = 0;
}
// Start at normal duty cycle
max_duty = new uint8_t[num_karts];
for (int i=0;i<num_karts;i++) {
max_duty[i] = DUTY_NORM;
}
}
// Start each kart with a unique place
cur_place = new uint8_t[num_karts];
for (int i=0;i<num_karts;i++) {
cur_place[i] = i+1;
}
/** This method checks if a kart is ready to play the game.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Ready state
*/
bool game::kart_is_ready (uint8_t kart_num) {
return kart_ready[kart_num-1];
}
/** This method initializes a kart's position and heading.
*
* NOTE: When a kart wants to join the game, it calls a function that
* returns its kart number. That function will call init_kart() using
* different values for kart_num until zero is returned.
*
* @param kart_num Number of kart (1, 2, ...)
* @param x_coord x-coordinate
* @param y_coord y-coordinate
* @param heading Compass heading
* @return 0 if successful
*
* Error codes:
* EOCC: xy-coordinates already occupied
* EINI: Kart already initialized
* EOOB: xy-coordinates out-of-bounds
* EINV: invalid kart number or heading
*/
uint8_t game::init_kart (uint8_t kart_num, uint8_t x_coord, uint8_t y_coord,
uint16_t heading) {
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return EINV;
}
if (heading >= 360) { // invalid heading
return EINV;
}
if (kart_ready[kart_num]) { // already initialized
return EINI;
}
if (x_coord > num_cols || y_coord > num_rows) { // out of bounds
return EOOB;
}
if (objectGrid->isOccupied(x_coord,y_coord)) { // coordinates occupied
return EOCC;
}
// Initialize kart
playerKart[kart_num]->setCoord(x_coord,y_coord);
playerKart[kart_num]->setDirection(heading);
// Add kart to grid
objectGrid->addObject(playerKart[kart_num]);
// Set ready status (subsequent calls to this method with same kart number
// will always fail)
kart_ready[kart_num] = true;
}
return 0;
/** This method displays a text-based representation of the grid. An 'x'
* corresponds to a wall, while a '.' corresponds to an unoccupied
* location. Any other grid object is labeled by its ID number.
*
* @param scale Scaling factor when printing to cout (0 to 100%)
*/
void game::display_occupancy (uint8_t scale) {
objectGrid->displayOccupancy (scale);
}
/** This method displays a list of all objects in the grid (does not include
* outer walls).
*/
void game::print_list (void) {
objectGrid->printList ();
}
/** This method manually adds an object to the grid.
*
* @param item Item type (see items.h)
* @param x_coord x-coordinate
* @param y_coord y-coordinate
* @param heading Compass heading
* @return 0 if successful
*
* Error codes:
* EOCC: xy-coordinates already occupied
* EINI: karts not yet initialized
* EOOB: xy-coordinates out-of-bounds
* EINV: invalid type or heading
*/
uint8_t game::add_object (item_values_t item, uint8_t x_coord, uint8_t y_coord,
uint16_t heading) {
// for red shells
uint8_t distance = INVALID;
uint8_t nearest = 0;
redShellObject* tempRed;
greenShellObject* tempGreen;
if (heading >= 360) { // invalid heading
return EINV;
}
if (x_coord > num_cols || y_coord > num_rows) { // out of bounds
return EOOB;
}
if (objectGrid->isOccupied(x_coord,y_coord)) { // coordinates occupied
return EOCC;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) { // karts not initialized
return EINI;
}
}
switch (item) {
case BANANA:
objectGrid->addObject (new bananaObject (x_coord, y_coord));
break;
case REDSHELL:
tempRed = new redShellObject (x_coord, y_coord,heading, NULL);
distance = INVALID;
nearest = 0;
for (int i=0;i<num_karts;i++) {
if (tempRed->getDistance(playerKart[i]) < distance) {
distance = tempRed->getDistance(playerKart[i]);
nearest = i;
}
}
tempRed->setTargetObject(playerKart[nearest]);
objectGrid->addObject (tempRed);
break;
case GREENSHELL:
tempGreen = new greenShellObject (x_coord, y_coord,heading);
distance = INVALID;
nearest = 0;
for (int i=0;i<num_karts;i++) {
if (tempGreen->getDistance(playerKart[i]) < distance) {
distance = tempGreen->getDistance(playerKart[i]);
nearest = i;
}
}
tempGreen->setTargetCoord(playerKart[nearest]->getX(),
playerKart[nearest]->getY());
objectGrid->addObject (tempGreen);
break;
case ITEMBOX:
objectGrid->addObject (new itemBoxObject (x_coord, y_coord));
break;
// invalid types
case NO_ITEM:
case WALL:
// hm, that would be interesting..
case MUSHROOM: // could be used as speed boosts?
case GREENSHIELD3:
case REDSHIELD3:
case KART:
case STAR:
// too much error checking
default:
return EINV;
break;
}
}
return 0;
/** This method returns an array of strings representing all the objects
* within a go-kart's viewing angle. The y-axis is parallel to the go-kart's
* heading, and the x-axis extends 90 degrees to the right of the driver.
*
* WARNING: Don't forget to free the string after using it. Also, edit once
* draw distance is known.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Array of strings (check config.h for max), NULL on error
*
* Format: t,x,y
* t: type of object (see items.h)
* x: x-coordinate (relative to go-kart)
* y: y-coordinate
*/
char** game::get_relative_coords (uint8_t kart_num) {
uint8_t count = 0; // number of strings
int16_t x = 0,y = 0; // relative coordinates
gridObject* cur;
char** xyArray = new char*[MAX_VIEW + 1];
char* str;
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return NULL;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return NULL;
}
}
for (int i=0;i<objectGrid->listLength();i++) {
cur = objectGrid->getFromList (i);
if (!playerKart[kart_num]->sameObject(cur)) {
y = playerKart[kart_num]->getRelativeY (cur);
if (y >= 0) {
// in front of kart
x = playerKart[kart_num]->getRelativeX (cur);
str = new char[14];
// make room for new string
if (cur->getType() != KART) {
sprintf (str, "%d,%d,%d", cur->getType(), x, y);
}
else { // kart
switch (cur->getShieldCount()) {
case 0: // unarmed
sprintf (str, "%d,%d,%d", KART, x, y);
break;
case 1:
if (cur->getShieldType() == REDSHELL) {
sprintf (str, "%d,%d,%d", REDSHIELD1, x, y);
}
else {
sprintf (str, "%d,%d,%d", GREENSHIELD1, x, y);
}
break;
case 2:
if (cur->getShieldType() == REDSHELL) {
sprintf (str, "%d,%d,%d", REDSHIELD2, x, y);
}
else {
sprintf (str, "%d,%d,%d", GREENSHIELD2, x, y);
}
break;
case 3:
if (cur->getShieldType() == REDSHELL) {
sprintf (str, "%d,%d,%d", REDSHIELD3, x, y);
}
else {
sprintf (str, "%d,%d,%d", GREENSHIELD3, x, y);
}
break;
case STAR:
sprintf (str, "%d,%d,%d", STAR, x, y);
break;
default:
break;
}
}
}
}
}
xyArray[count] = str;
// add to string array
if (++count >= MAX_VIEW) { // can't exceed max
break;
}
}
for (;count<MAX_VIEW;count++) {
xyArray[count] = new char[4];
sprintf (xyArray[count], "0,0,0");
}
return xyArray;
/** This method loads the kart with a item, if possible.
*
* @param kart_num Numer of kart (1, 2, ...)
* @param item New item
* @return 0 if successful
*
* Error codes:
* EOCC: kart already armed
* EINI: karts not yet initialized
* EINV: invalid kart number or item
*/
uint8_t game::set_item (uint8_t kart_num, item_values_t item) {
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return EINV;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
}
}
return EINI;
if (playerKart[kart_num]->getItem() != NO_ITEM) { // already armed
return EOCC;
}
switch (item) {
case BANANA:
case REDSHELL:
case REDSHIELD3:
case STAR:
case GREENSHELL:
case GREENSHIELD3:
case MUSHROOM:
playerKart[kart_num]->setItem (item);
break;
// invalid types
//case STAR:
case KART:
case ITEMBOX:
case WALL:
case NO_ITEM:
default:
return EINV;
}
}
return 0;
/** This method loads the kart with a random item, if possible.
*
* @param kart_num Numer of kart (1, 2, ...)
* @return 0 if successful
*
* Error codes:
* EOCC: kart already armed
* EINI: karts not yet initialized
* EINV: invalid kart number or item
*/
uint8_t game::set_item (uint8_t kart_num) {
item_values_t item;
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return EINV;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return EINI;
}
}
if (playerKart[kart_num]->getItem() != NO_ITEM) { // already armed
return EOCC;
}
srand ( time (NULL) );
do {
item = (item_values_t)(rand() % WALL);
switch (item) {
case BANANA:
case REDSHELL:
case REDSHIELD3:
case STAR:
case GREENSHELL:
case GREENSHIELD3:
case MUSHROOM:
playerKart[kart_num]->setItem (item);
return 0;
// invalid types
case KART:
case ITEMBOX:
case WALL:
case NO_ITEM:
default:
break;
}
}
while (1);
}
return 0;
/** This method changes kart positioning data, if possible.
*
* NOTE: No support for speed regulation has been added (yet).
*
* @param kart_num Number of kart (1, 2, ...)
* @param x_coord x-coordinate
* @param y_coord y-coordinate
* @param heading Compass heading
* @return 0 if successful
*
* Error codes:
* EOCC: xy-coordinates already occupied
* EINI: Karts not yet initialized
* EOOB: xy-coordinates out-of-bounds
* EINV: invalid kart number or heading
*/
uint8_t game::edit_kart (uint8_t kart_num, uint8_t x_coord, uint8_t y_coord,
uint16_t heading) {
gridObject* cur;
speed_thread_param_t *speed_param;
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return EINV;
}
if (heading >= 360) { // invalid heading
return EINV;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
}
}
return EINI;
if (x_coord > num_cols || y_coord > num_rows) { // out of bounds
return EOOB;
}
if (objectGrid->isOccupied(x_coord,y_coord)) { // occupied
if (playerKart[kart_num]->
// not moving
sameObject (objectGrid->get(x_coord,y_coord))) {
playerKart[kart_num]->setDirection (heading);
return 0;
}
cur = objectGrid->get (x_coord, y_coord);
switch (cur->getType()) {
case BANANA:
case REDSHELL:
case GREENSHIELD3:
case REDSHIELD3:
case GREENSHELL:
objectGrid->removeObject (cur);
objectGrid->moveObject (playerKart[kart_num],
x_coord, y_coord);
playerKart[kart_num]->setDirection(heading);
if (playerKart[kart_num]->hasShield ()) {
playerKart[kart_num]->useShield ();
}
else {
max_duty[kart_num] = DUTY_HALT;
speed_param = new speed_thread_param_t;
speed_param->p_game = this;
speed_param->kart_num = kart_num+1;
pthread_create (&temp_speed_thread, NULL,
reset_speed, (void *)speed_param);
}
return 0;
case ITEMBOX:
set_item (kart_num+1);
objectGrid->removeObject (cur);
objectGrid->moveObject (playerKart[kart_num],
x_coord, y_coord);
playerKart[kart_num]->setDirection(heading);
return 0;
case WALL:
case NO_ITEM:
case STAR:
case MUSHROOM:
case KART:
default:
objectGrid->moveObject (playerKart[kart_num],
x_coord, y_coord);
playerKart[kart_num]->setDirection(heading);
return 0;
}
}
else { // not occupied
objectGrid->moveObject (playerKart[kart_num],
x_coord, y_coord);
playerKart[kart_num]->setDirection(heading);
return 0;
}
}
find_collisions ();
/** This method creates the data packet for the specified kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @param data Buffer for packet (150 bytes long)
*/
char* game::generate_packet (uint8_t kart_num, char* data) {
char* temp;
char** coord_list;
uint8_t count;
item_values_t cur_item, cur_shield;
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return NULL;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return NULL;
}
}
// Get relative coordinates list
coord_list = get_relative_coords (kart_num+1);
memset (data, 0, sizeof(data));
// Change item labels to match image overlay
switch (playerKart[kart_num]->getItem()) {
case REDSHIELD3:
cur_item = MRSHELL;
break;
case GREENSHIELD3:
cur_item = MGSHELL;
break;
default:
cur_item = playerKart[kart_num]->getItem();
break;
}
switch (playerKart[kart_num]->getShieldType()) {
case REDSHELL:
cur_shield = MRSHELL;
break;
case GREENSHELL:
cur_shield = MGSHELL;
break;
default:
cur_shield = playerKart[kart_num]->getShieldType();
break;
}
// Compile HUD info
temp = new char[25];
sprintf (temp, "%d,%d,%d,%d,%d,%d,%d,",
kart_num+1,
cur_item,
cur_shield,
playerKart[kart_num]->getShieldCount(),
cur_lap[kart_num],
cur_place[kart_num],
max_duty[kart_num]);
strcpy (data,temp);
free (temp);
// Determine number of objects in view
for (count=0;count<MAX_VIEW;count++) {
temp = coord_list[count];
if (temp[0] == ',') { // first empty entry
break;
}
}
temp = new char[4];
sprintf (temp, "%d,", count);
strcat (data,temp);
free (temp);
// Add object coordinates
for (int i=0;i<MAX_VIEW;i++) {
if (i != MAX_VIEW-1) { // don't add last comma
temp = new char[13];
sprintf (temp, "%s,", coord_list[i]);
strcat (data, temp);
free (temp);
}
else {
strcat (data, coord_list[i]);
}
}
free (coord_list);
}
return data;
/** This method checks if any near-collisions have occured and reacts
* accordingly. The proximity needed for a "collision" is #define'd in
* config.h.
*
* @return Number of collisions detected
*/
uint8_t game::find_collisions (void) {
uint8_t count = 0; // number of collisions
uint8_t temp_x, temp_y;
//uint16_t temp_heading;
gridObject* obj1;
gridObject* obj2;
// remove any objects too close to wall
for (int i=0;i<objectGrid->listLength();i++) {
// Get current object
obj1 = objectGrid->getFromList (i);
if (obj1->getType () == KART) { // ignore karts
// do nothing
}
else {
}
}
// Too close to left or right boundaries
if (obj1->getX() < COLLISION ||
obj1->getX() > num_cols-COLLISION) {
if (obj1->getType () == GREENSHELL) {
if (obj1->getX () < COLLISION) { // left
if (obj1->getDirection () > 90 &&
obj1->getDirection () < 270) {
((greenShellObject*)obj1)->bounce (90);
}
}
else { // right
if (obj1->getDirection () < 90 ||
obj1->getDirection () > 270) {
((greenShellObject*)obj1)->bounce (90);
}
}
}
else {
objectGrid->removeObject (obj1);
i--;
}
}
// Too close to top or bottom boundaries
else if (obj1->getY() < COLLISION || obj1->getY() > num_rows-COLLISION) {
if (obj1->getType () == GREENSHELL) {
if (obj1->getY () < COLLISION) { // top
if (obj1->getDirection () < 180) {
((greenShellObject*)obj1)->bounce (0);
}
}
else { // bottom
if (obj1->getDirection () > 180 &&
obj1->getDirection () < 360) {
((greenShellObject*)obj1)->bounce (0);
}
}
}
else {
objectGrid->removeObject (obj1);
i--;
}
}
// handle two-object collisions
for (int i=0;i<objectGrid->listLength();i++) {
for (int j=i+1;j<objectGrid->listLength();j++) {
// Get current objects
obj1 = objectGrid->getFromList (i); // move outside of inner loop
obj2 = objectGrid->getFromList (j);
if (obj1->getDistance (obj2) <= COLLISION) { // under threshold
// Both karts (do nothing)
if (obj1->getType () == KART && obj2->getType () == KART) {
// do nothing
}
// No karts (only preserve walls)
else if (obj1->getType () != KART && obj2->getType () != KART) {
// Both walls (do nothing)
if (obj1->getType () == WALL && obj2->getType () == WALL) {
// do nothing
}
// No walls (remove both)
else if (obj1->getType () != WALL &&
obj2->getType () != WALL) {
objectGrid->removeObject (obj1);
objectGrid->removeObject (obj2);
i--;
count++;
break;
}
// One wall (remove other object IF NOT GREEN SHELL)
else if (obj1->getType () == WALL) {
// Check for green shell
objectGrid->removeObject (obj2);
j--;
count++;
}
else {
objectGrid->removeObject (obj1);
i--;
count++;
break;
}
}
}
}
}
// One kart (move kart to obstacle, move back)
else if (obj1->getType () == KART) { // obj1 is a kart
for (int k=0;k<num_karts;k++) {
if (obj1->sameObject (playerKart[k])) {
temp_x = obj1->getX ();
temp_y = obj1->getY ();
edit_kart(k+1,obj2->getX(),obj2->getY(),
obj1->getDirection());
edit_kart(k+1,temp_x,temp_y,
obj1->getDirection());
//j--;
count++;
break;
}
}
}
else { // obj2 is a kart
for (int k=0;k<num_karts;k++) {
if (obj2->sameObject (playerKart[k])) {
temp_x = obj2->getX ();
temp_y = obj2->getY ();
edit_kart(k+1,obj1->getX(),obj1->getY(),
obj1->getDirection());
edit_kart(k+1,temp_x,temp_y,
obj1->getDirection());
i--;
count++;
break;
}
}
break; // exit inner loop in case i = -1
}
}
return count;
/** This method updates all mobile object positions (except karts).
* Collisions are handled accordingly.
*
* @param elapsed_ms Time elapsed since last call (in milliseconds)
* @return Number of collisions detected
*/
uint8_t game::move_mobile (uint16_t elapsed_ms) {
uint8_t x,y;
float distance;
gridObject* cur;
//
//
//
//
//
// Update objects one at a time
for (int i=0;i<objectGrid->listLength();i++) {
cur = objectGrid->getFromList (i);
distance = (cur->getSpeed () * elapsed_ms) / 1000;
switch (cur->getType()) {
case REDSHELL:
// Update heading before moving
((redShellObject*) cur)->updateTrajectory ();
case GREENSHELL:
// Get expected x,y
x = cur->getX() + (distance *
cos(cur->getDirection()*PI/180));
y = cur->getY() - (distance *
sin(cur->getDirection()*PI/180));
cout << "Old: (" << cur->getX() << ","
<< cur->getY() << ")" << endl;
cout << "New: (" << x << "," << y << ")" << endl;
cout << "Next location: (" << x << "," << y << ")" << endl;
cout << "Distance traveled: " << distance << endl;
// Stop at walls
if (x < 1) {
x = 1;
}
else if (x > num_cols - 2) {
x = num_cols - 2;
}
if (y < 1) {
y = 1;
}
else if (y > num_rows - 2) {
y = num_cols - 2;
}
// Move shell
objectGrid->moveObject (cur,x,y);
break;
case KART: // may later include support for ghost karts
break;
default:
break;
}
}
}
return find_collisions();
/** This method causes a kart to use its item, if one is available.
*
* @param kart_num Number of kart (1, 2, ...)
* @return 0 if successful
*
* Error codes:
* EOCC: xy-coordinates already occupied
* EINI: Karts not yet initialized
* EOOB: xy-coordinates out-of-bounds
* EINV: invalid kart number or item cannot be used
* ENOI: kart has no item
*/
uint8_t game::use_item (uint8_t kart_num) {
gridObject* cur;
speed_thread_param_t *speed_param;
item_values_t next_item;
// for red shells
uint8_t distance;
uint8_t nearest;
// Kart numbers start at zero
kart_num--;
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return EINI;
}
}
if (playerKart[kart_num]->hasShield()) {
next_item = playerKart[kart_num]->getShieldType();
}
else {
next_item = playerKart[kart_num]->getItem();
}
switch (next_item) {
case NO_ITEM:
// use shield if possible
if (!playerKart[kart_num]->hasShield()) {
return ENOI;
}
case BANANA:
cur = playerKart[kart_num]->useItem();
if (cur == NULL) {
return EINV;
}
objectGrid->addObject (cur);
break;
case REDSHELL:
cur = playerKart[kart_num]->useItem();
if (cur == NULL) {
return EINV;
}
distance = INVALID;
for (int i=0;i<num_karts;i++) {
if (i != kart_num) { // don't target yourself, of course
if (cur->getDistance(playerKart[i]) < distance) {
distance = cur->getDistance(playerKart[i]);
nearest = i;
}
}
}
((redShellObject*)cur)->setTargetObject (playerKart[nearest]);
//
//
//
//
//
//
//
//
//
//
//
}
objectGrid->addObject (cur);
break;
case GREENSHELL:
cur = playerKart[kart_num]->useItem();
if (cur == NULL) {
return EINV;
}
distance = INVALID;
for (int i=0;i<num_karts;i++) {
if (i != kart_num) { // don't target yourself, of course
if (cur->getDistance(playerKart[i]) < distance) {
distance = cur->getDistance(playerKart[i]);
nearest = i;
}
}
}
((greenShellObject*)cur)->setTargetCoord
(playerKart[nearest]->getX (), playerKart[nearest]->getY());
objectGrid->addObject (cur);
break;
case GREENSHIELD3:
case REDSHIELD3:
playerKart[kart_num]->useItem();
break;
case STAR:
playerKart[kart_num]->useItem();
if (playerKart[kart_num]->getShieldType() == STAR) {
max_duty[kart_num] = DUTY_BOOST;
speed_param = new speed_thread_param_t;
speed_param->p_game = this;
speed_param->kart_num = kart_num+1;
pthread_create (&temp_speed_thread, NULL,
reset_speed, (void *)speed_param);
}
break;
case MUSHROOM:
playerKart[kart_num]->useItem();
max_duty[kart_num] = DUTY_BOOST;
speed_param = new speed_thread_param_t;
speed_param->p_game = this;
speed_param->kart_num = kart_num+1;
pthread_create (&temp_speed_thread, NULL,
reset_speed, (void *)speed_param);
break;
case KART:
case ITEMBOX:
case WALL:
default:
return EINV;
}
return 0;
/** This method gets the current lap for the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Current lap (0, 1, 2, ...)
*
* Error codes:
* EINI: Karts not yet initialized
* EINV: invalid kart number
*/
uint8_t game::get_lap (uint8_t kart_num) {
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return EINV;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return EINI;
}
}
}
return cur_lap[kart_num];
/** This method begins the game.
*
* @return 0 if successful
*
* Error codes:
* EINI: Karts not yet initialized
* EINV: Game already started
*/
uint8_t game::start_game (void) {
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return EINI;
}
if (cur_lap[i]) { // game already started
return EINV;
}
}
for (int i=0;i<num_karts;i++) { // all lap counts set to one
cur_lap[i]++;
}
return 0;
}
/** This method increments the lap counter for the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Current lap (0, 1, 2, ...)
*
* Error codes:
* EINI: Karts not yet initialized
* EINV: invalid kart number or game not yet started
*/
uint8_t game::next_lap (uint8_t kart_num) {
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return EINV;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return EINI;
}
}
}
if (!cur_lap[kart_num]) { // game not yet started
return EINV;
}
return ++cur_lap[kart_num];
/** This method changes the maximum duty cycle for the corresponding kart.
* See duty cycle #define's in config.h.
*
* @param kart_num Number of kart (1, 2, ...)
* @param new_duty New duty cycle
* @return 0 if successful
*
* Error codes:
* EINI: Karts not yet initialized
* EINV: Invalid kart number, game not yet started, or invalid duty cycle
*/
uint8_t game::set_max_duty (uint8_t kart_num, uint8_t new_duty) {
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return EINV;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return EINI;
}
}
if (!cur_lap[kart_num]) { // game not yet started
return EINV;
}
}
if (new_duty > 100) { // invalid duty cycle
return EINV;
}
max_duty[kart_num] = new_duty;
return 0;
/** This method gets the x-coordinate for the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return x-coordinate of kart
*
* NOTE: Will return 0 upon error (invalid kart number, kart not
* initialized, etc.
*/
uint8_t game::get_x (uint8_t kart_num) {
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return 0;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return 0;
}
}
}
return playerKart[kart_num]->getX ();
/** This method gets the y-coordinate for the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return y-coordinate of kart
*
* NOTE: Will return 0 upon error (invalid kart number, kart not
* initialized, etc.
*/
uint8_t game::get_y (uint8_t kart_num) {
// Kart numbers start at zero
kart_num--;
}
if (kart_num >= num_karts) { // invalid kart number
return 0;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return 0;
}
}
return playerKart[kart_num]->getY ();
/** This method gets the speed of the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Speed of kart
*
* NOTE: Will return 0 upon error (invalid kart number, kart not
* initialized, etc.
*/
uint8_t game::get_speed (uint8_t kart_num) {
// Kart numbers start at zero
kart_num--;
}
if (kart_num >= num_karts) { // invalid kart number
return 0;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return 0;
}
}
return playerKart[kart_num]->getSpeed ();
/** This method gets the heading of the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Heading of kart
*
* NOTE: Will return 0 upon error (invalid kart number, kart not
* initialized, etc.
*/
uint16_t game::get_heading (uint8_t kart_num) {
// Kart numbers start at zero
kart_num--;
}
if (kart_num >= num_karts) { // invalid kart number
return 0;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return 0;
}
}
return playerKart[kart_num]->getDirection ();
/** This method gets the current item of the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Current item
*
* NOTE: Will return NO_ITEM upon error (invalid kart number, kart not
* initialized, etc.
*/
item_values_t game::get_item (uint8_t kart_num) {
// Kart numbers start at zero
kart_num--;
}
if (kart_num >= num_karts) { // invalid kart number
return NO_ITEM;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return NO_ITEM;
}
}
return playerKart[kart_num]->getItem ();
/** This method gets the current shield type of the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Current shield
*
* NOTE: Will return NO_ITEM upon error (invalid kart number, kart not
* initialized, etc.
*/
item_values_t game::get_shield_type (uint8_t kart_num) {
// Kart numbers start at zero
kart_num--;
}
if (kart_num >= num_karts) { // invalid kart number
return NO_ITEM;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return NO_ITEM;
}
}
return playerKart[kart_num]->getShieldType ();
/** This method gets the current shield count of the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Current shield count
*/
uint8_t game::get_shield_count (uint8_t kart_num) {
// Kart numbers start at zero
kart_num--;
}
if (kart_num >= num_karts) { // invalid kart number
return 0;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return 0;
}
}
return playerKart[kart_num]->getShieldCount ();
/** This method generates a packet filled with kart information. This data
* is used by the ghost karts, but can also be used by the graphics code
* to display overhead info on the HUD.
*
* @param kart_num Number of kart (1, 2, ...)
* @param data Buffer for packet/string (40 bytes long)
*
* Error codes:
* EINI: Karts not yet initialized
* EINV: invalid kart number or buffer too small
*/
uint8_t game::generate_ghost_packet (uint8_t kart_num, char* data) {
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return EINV;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return EINI;
}
}
// if (strlen (data) > MAX_PACKET_GHOST) {
//
return EINV;
// }
// Clear buffer
memset (data, 0, MAX_PACKET_GHOST);
}
sprintf (data, "%d,%d,%d,%d,%d,%d,%d,%d",
playerKart[kart_num]->getX(),
playerKart[kart_num]->getY(),
playerKart[kart_num]->getDirection(),
playerKart[kart_num]->getSpeed(),
playerKart[kart_num]->getItem(),
playerKart[kart_num]->getShieldType(),
playerKart[kart_num]->getShieldCount(),
cur_lap[kart_num]);
return 0;
/** This method disables any speed boosts (MUSHROOM, STAR) for the
* corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return 0 if successful
*
* Error codes:
* EINI: Karts not yet initialized
* EINV: invalid kart number or no boost
*/
uint8_t game::end_boost (uint8_t kart_num) {
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return EINV;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return EINI;
}
}
if (playerKart[kart_num]->starPowerStop()) {
max_duty[kart_num] = DUTY_NORM;
return 0;
}
}
return EINV;
/** This method changes a kart's place in the game (1st, 2nd, 3rd, etc.)
* without affecting the status of the other karts. Thus, multiple karts
* can be in first place. A game grid class is responsible for calling
* this method individually for each kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return 0 if successful
*
* Error codes:
* EINI: Karts not yet initialized
* EINV: Invalid kart number
*/
uint8_t game::set_place (uint8_t kart_num, uint8_t place) {
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return EINV;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return EINI;
}
}
cur_place[kart_num] = place;
return 0;
}
/** This method gets a kart's current lap.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Kart's lap
*/
uint8_t game::get_place (uint8_t kart_num) {
}
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) { // invalid kart number
return EINV;
}
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return EINI;
}
}
return cur_place[kart_num];
/** This method creates the data packet for a 2D overhead view.
*
* @param data Buffer for packet
*
* Error codes:
* EINI: Karts not yet initialized
*/
uint8_t game::generate_2D_packet (char* data) {
gridObject* cur;
item_values_t cur_type;
uint8_t index;
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return NULL;
}
}
memset (data, 0, MAX_PACKET_2D);
index = 0;
// Start with header
index += sprintf (data+index, "SO,");
// Send current item
index += sprintf (data+index, "%d,%d,", playerKart[0]->getItem(),
playerKart[1]->getItem());
// Select format (only "all" is supported for now)
index += sprintf (data+index, "A,");
// Add random element for now
index += sprintf (data+index, "0,");
for (int i=0;i<objectGrid->listLength();i++) {
cur = objectGrid->getFromList (i);
cur_type = cur->getType();
if (cur_type == KART) {
// Replace kart with shield type, if applicable
if (((kartObject*)cur)->hasShield()) {
cur_type = ((kartObject*)cur)->getShieldType();
}
}
index += sprintf (data+index, "%d,%d,%d", cur_type, cur->getX(),
cur->getY());
if (i != objectGrid->listLength()-1) {
index += sprintf (data+index, ",");
}
}
}
return 0;
/** This method creates the data packet for the RC controller.
*
* @param data Buffer for packet
*
* Error codes:
* EINI: Karts not yet initialized
*/
int game::generate_RC_packet (char *data) {
unsigned int index;
unsigned int RC_speed;
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return NULL;
}
}
memset (data, 0, MAX_PACKET_RC);
index = 0;
// Start with header
index += sprintf (data+index, "SRC,");
// Add random element for now
index += sprintf (data+index, "0,");
for (int i=0;i<num_karts;i++) {
// Kart number
index += sprintf (data+index, "%d,", i+1);
// Get speed regulation byte
switch (max_duty[i]) {
case DUTY_BOOST:
RC_speed = RC_FULL;
break;
case DUTY_NORM:
case DUTY_SLOW:
RC_speed = RC_HALF;
break;
case DUTY_HALT:
default:
RC_speed = RC_STOP;
break;
}
index += sprintf (data+index, "%d", RC_speed);
if (i != num_karts-1) { // Not last entry
index += sprintf (data+index, ",");
}
}
}
return 0;
/** This method returns a pointer to the grid. The controller can use this
* method for direct access to grid information.
*
* @return Pointer to grid class object
*/
grid *game::get_grid (void) {
return objectGrid;
}
/** This thread waits for a set amount of time before restoring a kart's
* normal speed.
*
* @param arg Pointer to struct speed_thread_param_t
*/
void *reset_speed (void *arg) {
speed_thread_param_t *game_info = (speed_thread_param_t*)arg;
game *game_model = game_info->p_game;
unsigned int kart_num = game_info->kart_num;
}
struct timespec *delay = new timespec;
if (game_model->get_shield_type (kart_num) == STAR) {
delay->tv_sec = STAR_MS / 1000;
delay->tv_nsec = STAR_MS % 1000000000;
}
else {
delay->tv_sec = MUSHROOM_MS / 1000;
delay->tv_nsec = MUSHROOM_MS % 1000000000;
}
nanosleep (delay,NULL);
game_model->set_max_duty (kart_num, DUTY_NORM);
game_model->end_boost (kart_num);
return NULL;
/** This method creates the data packet for the iOS 2D overhead view.
*
* @param data Buffer for packet
*
* Error codes:
* EINI: Karts not yet initialized
*/
uint8_t game::generate_2D_iOS_packet (char* data) {
gridObject* cur;
item_values_t cur_type;
uint8_t index;
for (int i=0;i<num_karts;i++) {
if (!kart_ready[i]) {
// karts not initialized
return NULL;
}
}
memset (data, 0, MAX_PACKET_2D);
index = 0;
// Start with header
index += sprintf (data+index, "S2D,");
// Send current item
index += sprintf (data+index, "%d,%d,", playerKart[0]->getItem(),
playerKart[1]->getItem());
// Send dimensions
index += sprintf (data+index, "%d,%d,", num_cols, num_rows);
// Send items (max 10)
for (int i=0;i<min((int)objectGrid->listLength(),10);i++) {
cur = objectGrid->getFromList (i);
cur_type = cur->getType();
if (cur_type == KART) {
// Replace kart with shield type, if applicable
if (((kartObject*)cur)->hasShield()) {
cur_type = ((kartObject*)cur)->getShieldType();
}
}
index += sprintf (data+index, "%d,%d,%d", cur_type, cur->getX(),
cur->getY());
if (i != objectGrid->listLength()-1) {
index += sprintf (data+index, ",");
}
}
}
ii.
return 0;
controller.cpp
/* @file controller.cpp
* @details This class contains the functionality needed for the controller
*
of a model-view-controller (MVC) architecture. Public methods decode
*
packets from both physical and virtual (keyboard-controlled) karts
*
and call the correct methods of the game class to synchronize the game
*
state with the information local to each kart.
* @author David Allender
* @author Joryl Calizo
* @date April 11, 2011
*/
#include "controller.h"
#ifdef USE_GUI
struct mouse_input_t {
bool waiting;
int x;
int y;
};
using namespace cv;
void mouse_handler (int, int, int, int, void*);
#endif
/** This constructor creates a controller object.
*
* @param p_game Pointer to game model
*/
controller::controller (game *p_game) {
// Store local pointer to game model
game_model = p_game;
game_grid = game_model->get_grid ();
}
/** This method initializes the game by getting settings from the user via
* keyboard inputs.
*
* @param game_type Code for game type (see below)
* @return Number of karts
*
* Game type codes:
* GAME_KART: using real karts
* GAME_RC: using RC cars
*/
unsigned int controller::init_game (unsigned int game_type) {
// Greeting
cout << "Hello!" << endl;
// Determine number of karts
cout << "How many karts will be playing? ";
cin >> num_karts;
num_karts -= '0'; // convert to int
while (num_karts != 2) { // support for two karts only
cout << "Sorry, invalid input. Please enter a number between 2 "
"and 2. ";
cin >> num_karts;
num_karts -= '0';
}
this->game_type = game_type;
if (game_type == GAME_KART) {
#ifndef NO_NETWORK
// Determine number of physical karts
cout << "How many are physical karts? ";
cin >> num_real_karts;
num_real_karts -= '0';
while (num_real_karts > 2) {
cout << "Sorry, invalid input. Please enter a number between 0 "
"and 2. ";
cin >> num_real_karts;
num_real_karts -= '0';
}
#else
num_real_karts = 0;
#endif
num_ghost_karts = 2-num_real_karts;
// Create networking objects
#ifndef NO_NETWORK
kart = new networking*[num_karts]; // always create two karts
for (int i=0;i<num_karts;i++) {
kart[i] = new networking (NETWORK_SERVER,i+1);
kart[i]->connect();
}
#endif
// Why output EOOB?
cout << game_model->init_kart (1,20,20,0) << endl;
game_model->init_kart (2,game_grid->get_cols()-20,
game_grid->get_rows()-20,180);
}
else if (game_type == GAME_RC) {
num_RC = num_karts;
#ifndef NO_NETWORK
cout << "Connecting to Overhead View..." << endl;
overhead = new networking (NETWORK_SERVER,1); // first slot
overhead->connect ();
#ifdef USE_RC_CTRL
cout << "Connecting to RC controller..." << endl;
RC_controller = new networking (NETWORK_SERVER,2);
RC_controller->connect ();
#endif
#endif
game_model->init_kart (1,20,20,0);
game_model->init_kart (2,game_grid->get_cols()-20,
game_grid->get_rows()-20,180);
}
#ifdef USE_GUI
view.create (600,600,CV_8UC3);
view_label = "Server View";
namedWindow (view_label, CV_GUI_EXPANDED);
#endif
return num_karts;
}
/** This function receives from the networking stack
* and calls "edit_kart" to change the game state
*/
void controller::update_game(void){
#ifdef USE_GUI
gui_occupancy ();
#endif
if (game_type == GAME_KART) {
uint32_t x, y;
uint16_t heading;
uint32_t speed;
uint8_t item_used = 0;
int received = 0;
char *buffer = (char*) malloc (MAX_PACKET_GHOST);
char *pch;
char *tok;
int tokcount = 0;
uint8_t num_commas = 0;
memset (buffer, 0 , MAX_PACKET_GHOST);
for (int i=0;i<num_real_karts;i++) {
item_used = 0;
received = kart[i]->recv_data(buffer);
#ifdef DEBUG
cout << "Bytes received: " << received << endl;
cout << "Packet from kart " << i+1 << ": " << buffer << endl;
#endif
// Count number of commas
num_commas = 0;
pch = strchr (buffer,',');
while (pch != NULL) {
num_commas++;
pch = strchr(pch+1,',');
}
if (num_commas == 4)
{
tokcount = 0;
tok = strtok(buffer, ",");
while(tok)
{
if(tokcount == 0)
{
x = atoi(tok);
}else if(tokcount == 1)
{
y = atoi(tok);
}else if(tokcount == 2)
{
heading = atoi(tok);
}else if(tokcount == 3)
{
speed = atoi(tok);
}else if(tokcount == 4)
{
item_used = atoi(tok);
}
tok = strtok(NULL, ",");
tokcount++;
}
game_model->edit_kart(i+1, x, y, heading);
if (item_used) {
game_model->use_item (i+1);
}
received = 0;
}
else {
#ifdef DEBUG
cout << "Bad packet." << endl;
#endif
}
memset (buffer, 0 , MAX_PACKET_GHOST);
}
free (buffer);
}
else if (game_type == GAME_RC) {
char *cur_tok;
char *buffer = new char[MAX_PACKET_OVERHEAD];
unsigned int *x, *y, *speed, *heading, num_RC, cur_RC, count;
bool *item_used;
unsigned int tok_count = 0;
count = 0;
memset (buffer, 0, MAX_PACKET_OVERHEAD);
// Get packet from 2D program
#ifndef NO_NETWORK
count = overhead->recv_data(buffer);
#endif
//count = sprintf(buffer, "O,2,0,1,123,102,197,0,1,2,54,67,349,0,1");
#ifdef DEBUG
cout << "Bytes received: " << count << endl;
cout << "Packet from 2D: " << buffer << endl;
#endif
// Get first token
cur_tok = strtok (buffer, ",");
while (cur_tok) {
if (tok_count == 0) { // header
if (cur_tok[0] != 'O') {
return;
}
}
else if (tok_count == 1) {
num_RC = atoi(cur_tok); // should be two
x = new unsigned int[num_RC];
y = new unsigned int[num_RC];
speed = new unsigned int[num_RC];
heading = new unsigned int[num_RC];
item_used = new bool[num_RC];
}
else if (tok_count == 2) { // unused (for now)
}
else if (tok_count > 2) { // karts
// Kart number (starts at one)
cur_RC = atoi(cur_tok)-1;
cur_tok = strtok (NULL, ",");
tok_count++;
if (!cur_tok) {
return;
}
// x-coordinate
x[cur_RC] = atoi (cur_tok);
cur_tok = strtok (NULL, ",");
tok_count++;
if (!cur_tok) {
return;
}
// y-coordinate
y[cur_RC] = atoi (cur_tok);
cur_tok = strtok (NULL, ",");
tok_count++;
if (!cur_tok) {
return;
}
// heading
heading[cur_RC] = atoi (cur_tok);
cur_tok = strtok (NULL, ",");
tok_count++;
if (!cur_tok) {
return;
}
// speed
speed[cur_RC] = atoi (cur_tok);
cur_tok = strtok (NULL, ",");
tok_count++;
if (!cur_tok) {
return;
}
// item used
item_used[cur_RC] = atoi (cur_tok);
}
// Get next token
}
}
}
cur_tok = strtok (NULL, ",");
tok_count++;
// Execute
for (unsigned int i=0;i<num_RC;i++) {
game_model->edit_kart (i+1,x[i],y[i],heading[i]);
if (item_used[i]) {
game_model->use_item (i+1);
}
}
free (buffer);
/* This function calls generate_packet(for player)
* and generate_ghost_packet(for ghosts)
* to generate packets. Then the user sends that to the kart
* where it will be parsed by Joseph Joshua Abad
*/
void controller::update_karts(void){
if (game_type == GAME_KART) {
char packet[MAX_PACKET_SIZE];
for (int i=0;i<num_real_karts;i++) {
memset (packet,0,MAX_PACKET_SIZE);
game_model->generate_packet (i+1, packet);
#ifdef DEBUG
cout << "packet: " << packet << endl;
#endif
kart[i]->send_data (packet);
}
//GHOST KART IS NOW IOS DEVICE
for (int i=0;i<num_ghost_karts;i++) {
#ifndef NO_NETWORK
// Generate pseudo-ghost info
memset (packet,0,MAX_PACKET_SIZE);
game_model->generate_packet (i+1+num_real_karts, packet);
kart[1]->send_data(packet);
game_model->generate_2D_iOS_packet (packet);
kart[1]->send_data(packet);
#ifdef DEBUG
cout << "packet: " << packet << endl;
#endif
//kart[i+num_real_karts]->send_data (packet);
#endif
}
}
else if (game_type == GAME_RC) {
char packet[MAX_PACKET_2D];
memset (packet,0,MAX_PACKET_2D);
game_model->generate_2D_packet (packet);
#ifdef DEBUG
cout << "Packet for 2D view: " << packet << endl;
#endif
#ifndef NO_NETWORK
overhead->send_data (packet);
#endif
memset (packet,0,MAX_PACKET_2D);
}
game_model->generate_RC_packet (packet);
#ifdef DEBUG
cout << "Packet for RC controller: " << packet << endl;
#endif
#ifndef NO_NETWORK
#ifdef USE_RC_CTRL
RC_controller->send_data (packet);
#endif
#endif
}
/** This method displays an overhead, text-based representation of the
* playing grid.
*
* @param scale Scaling factor (0 to 100%)
*/
void controller::display_occupancy (uint8_t scale) {
game_model->display_occupancy (scale);
}
/** This method prints a list of all the objects currently in the grid.
*/
void controller::print_list (void) {
game_model->print_list ();
}
/** This method begins the game.
*
* @return 0 if successful
*
* Error codes:
* EINI: Karts not yet initialized
* EINV: Game already started
*/
uint8_t controller::start_game (void) {
return game_model->start_game ();
}
/** This method creates a text-based interface for manually changing the
* game state.
*/
void controller::main_ui (void) {
char string[MAX_PACKET_2D];
uint8_t temp;
char input;
cout << "Enter a command. 'H' prints out a list of commands." << endl;
while (true) {
memset (string, 0, MAX_PACKET_2D);
#ifndef USE_GUI
cout << "> ";
cin >> input;
#else
do {
imshow (view_label, view);
input = waitKey (500);
} while (input == -1);
#endif
switch (input) {
case 'H':
case 'h':
//
cout << "Press 'G' to control ghost kart." << endl;
cout << "Press 'K' to make changes to a kart." << endl;
cout << "Press 'L' to display list of grid objects." << endl;
cout << "Press 'O' to display an occupancy grid." << endl;
cout << "Press 'A' to add new objects to the grid." << endl;
cout << "Press 'S' to turn off STAR power." << endl;
cout << "Press 'P' to change kart places." << endl;
cout << "Press 'U' to generate a 2D overhead view packet." << endl;
cout << "Press 'I' to generate an iOS 2D overhead view packet." << endl;
cout << "Press '1 or 2' to generate a HUD packet." << endl;
break;
case 'K':
case 'k':
cout << "Which kart would you like to control?" << endl;
do {
#ifndef USE_GUI
cout << "> ";
cin >> input;
#else
do {
imshow (view_label, view);
input = waitKey (500);
} while (input == -1);
#endif
}
while ((input - '0') > num_karts || !(input - '0'));
control_kart (input - '0');
break;
case 'L':
case 'l':
print_list ();
break;
case 'O':
case 'o':
display_occupancy (25);
break;
case 'S':
case 's':
game_model->end_boost(1);
game_model->end_boost(2);
break;
case 'A':
case 'a':
add_object();
break;
case 'P':
case 'p':
for (int i=1;i<=num_karts;i++) {
temp = game_model->get_place(i);
if (temp == num_karts) {
game_model->set_place(i,1);
}
else {
game_model->set_place(i,temp+1);
}
}
break;
case 'U':
}
}
}
case 'u':
game_model->generate_2D_packet(string);
cout << string << endl;
break;
case 'I':
case 'i':
game_model->generate_2D_iOS_packet(string);
cout << string << endl;
break;
case '1':
case '2':
game_model->generate_packet(input-'0',string);
cout << string << endl;
break;
default:
cout << "Invalid or unsupported command. Sorry." << endl;
break;
/** This method allows a user to change kart settings.
*
* @param kart_num Number of kart (1, 2, ...)
*/
void controller::control_kart (uint8_t kart_num) {
char input;
uint8_t result;
#ifdef USE_GUI
mouse_input_t *mouse_click = new mouse_input_t;
mouse_input_t *mouse_unclick = new mouse_input_t;
signed int x,y,heading;
#endif
// Kart numbers start at zero
kart_num--;
if (kart_num >= num_karts) {
return;
}
cout << "Controlling kart " << kart_num + 1 << "." << endl;
while (true) {
#ifndef USE_GUI
cout << "> ";
cin >> input;
#else
do {
imshow (view_label, view);
input = waitKey(500);
} while (input == -1);
#endif
switch (input) {
case 'H':
case 'h':
cout << "Press 'S' to move kart." << endl;
cout << "Press 'D' to change compass direction." << endl;
cout << "Press 'L' to move on to the next lap." << endl;
cout << "Press '+' to remove speed regulation." << endl;
cout << "Press '-' to limit speed." << endl;
cout << "Press 'X' to stop kart." << endl;
cout << "Press '*' to get a star." << endl;
cout << "Press 'B' to get a banana." << endl;
cout << "Press 'R' to get a red shell." << endl;
cout << "Press 'G' to get a green shell." << endl;
cout << "Press '3' to get a triple red shell." << endl;
cout << "Press '4' to get a triple green shell." << endl;
cout << "Press 'M' to get a mushroom." << endl;
cout << "Press '?' to get a random item." << endl;
cout << "Press 'U' to use item." << endl;
cout << "Press 'C' to exit." << endl;
break;
case 'D':
case 'd':
// Create drag algorithm
mouse_click->waiting = true;
setMouseCallback (view_label, mouse_handler, mouse_click);
cout << "Drag cursor in direction desired." << endl;
do {
imshow (view_label, view);
waitKey (500);
} while (mouse_click->waiting);
setMouseCallback (view_label, mouse_handler, mouse_unclick);
mouse_unclick->waiting = true;
do {
imshow (view_label, view);
waitKey (500);
} while (mouse_unclick->waiting);
setMouseCallback (view_label, NULL, NULL);
heading = atan2 (mouse_click->y - mouse_unclick->y,
mouse_unclick->x - mouse_click->x)
* 180 / PI;
if (heading < 0) {
heading += 360;
}
cout << "New heading: " << heading << endl;
game_model->edit_kart (kart_num+1, get_x (kart_num+1),
get_y (kart_num+1), heading);
break;
case 'S':
case 's':
mouse_click->waiting = true;
setMouseCallback (view_label, mouse_handler, mouse_click);
cout << "Click on new coordinates." << endl;
do {
imshow (view_label, view);
waitKey (500);
} while (mouse_click->waiting);
setMouseCallback (view_label, NULL, NULL);
x = mouse_click->x * game_grid->get_cols () / 600;
y = mouse_click->y * game_grid->get_rows () / 600;
game_model->edit_kart (kart_num+1,x,y,get_heading(kart_num+1));
break;
case 'L':
case 'l':
game_model->next_lap (kart_num+1);
cout << "Kart " << kart_num + 1 << "'s lap incremented."
<< endl;
break;
case '+':
game_model->set_max_duty (kart_num+1, DUTY_BOOST);
cout << "Kart " << kart_num + 1 << " set to max duty cycle."
<< endl;
break;
case '-':
game_model->set_max_duty (kart_num+1, DUTY_NORM);
cout << "Kart " << kart_num + 1 << " set to normal duty cycle."
<< endl;
break;
case 'X':
case 'x':
game_model->set_max_duty (kart_num+1, DUTY_HALT);
cout << "Kart " << kart_num + 1 << " set to zero duty cycle."
<< endl;
break;
case '*':
result = game_model->set_item (kart_num+1, STAR);
switch (result) {
case 0:
cout << "Kart " << kart_num + 1 << " received a star."
<< endl;
break;
case EOCC:
cout << "Kart " << kart_num + 1 << " already armed."
<< endl;
break;
default:
break;
}
break;
case 'B':
case 'b':
result = game_model->set_item (kart_num+1, BANANA);
switch (result) {
case 0:
cout << "Kart " << kart_num + 1 << " received a "
<< "banana." << endl;
break;
case EOCC:
cout << "Kart " << kart_num + 1 << " already armed."
<< endl;
break;
default:
break;
}
break;
case 'G':
case 'g':
game_model->set_item (kart_num+1, GREENSHELL);
break;
case 'R':
case 'r':
result = game_model->set_item (kart_num+1, REDSHELL);
switch (result) {
case 0:
cout << "Kart " << kart_num + 1 << " received a "
<< "red shell." << endl;
break;
case EOCC:
cout << "Kart " << kart_num + 1 << " already armed."
<< endl;
break;
default:
break;
}
break;
case '3':
result = game_model->set_item (kart_num+1, REDSHIELD3);
switch (result) {
case 0:
cout << "Kart " << kart_num + 1 << " received "
<< "red shell x3." << endl;
break;
case EOCC:
cout << "Kart " << kart_num + 1 << " already armed."
<< endl;
break;
default:
break;
}
break;
case '4':
result = game_model->set_item (kart_num+1, GREENSHIELD3);
switch (result) {
case 0:
cout << "Kart " << kart_num + 1 << " received "
<< "green shell x3." << endl;
break;
case EOCC:
cout << "Kart " << kart_num + 1 << " already armed."
<< endl;
break;
default:
break;
}
break;
case 'M':
case 'm':
result = game_model->set_item (kart_num+1, MUSHROOM);
switch (result) {
case 0:
cout << "Kart " << kart_num + 1 << " received "
<< "a mushroom." << endl;
break;
case EOCC:
cout << "Kart " << kart_num + 1 << " already armed."
<< endl;
break;
default:
break;
}
break;
case '?':
result = game_model->set_item (kart_num+1);
switch (result) {
case 0:
cout << "Kart " << kart_num + 1 << " received a "
<< "random item." << endl;
break;
case EOCC:
cout << "Kart " << kart_num + 1 << " already armed."
<< endl;
break;
default:
break;
}
}
}
}
break;
case 'U':
case 'u':
game_model->use_item (kart_num+1);
break;
case 'C':
case 'c':
cout << "Returning to main menu." << endl;
return;
default:
break;
/** This method allows a user to manually add an object to the grid.
*/
void controller::add_object (void) {
char input;
#ifndef USE_GUI
char temp[4];
#endif
uint8_t x,y;
uint8_t result;
item_values_t new_item;
#ifdef USE_GUI
mouse_input_t *mouse_click = new mouse_input_t;
#endif
cout << "Adding objects." << endl;
while (true) {
#ifndef USE_GUI
cout << "> ";
cin >> input;
#else
do {
imshow (view_label, view);
input = waitKey (500);
} while (input == -1);
#endif
switch (input) {
case 'H':
case 'h':
cout << "Press 'B' to add a banana." << endl;
cout << "Press 'R' to add a red shell." << endl;
cout << "Press 'G' to add a green shell." << endl;
cout << "Press 'I' to add an item box." << endl;
cout << "Press 'C' to exit." << endl;
continue;
case 'C':
case 'c':
cout << "Returning to main menu." << endl;
return;
case 'B':
case 'b':
case 'R':
case 'r':
case 'G':
case 'g':
case 'I':
case 'i':
#ifdef USE_GUI
mouse_click->waiting = true;
setMouseCallback (view_label, mouse_handler, mouse_click);
cout << "Click to drop item." << endl;
do {
imshow (view_label, view);
waitKey(500);
} while (mouse_click->waiting);
x = mouse_click->x * game_grid->get_cols () / 600;
y = mouse_click->y * game_grid->get_rows () / 600;
setMouseCallback (view_label, NULL, NULL);
#else
cout << "X: ";
cin >> temp;
x = atoi (temp);
cout << "Y: ";
cin >> temp;
y = atoi (temp);
#endif
break;
default:
cout << "Invalid character, try again." << endl;
continue;
}
switch (input) {
case 'B':
case 'b':
new_item = BANANA;
break;
case 'R':
case 'r':
new_item = REDSHELL;
break;
case 'G':
case 'g':
new_item = GREENSHELL;
break;
case 'I':
case 'i':
new_item = ITEMBOX;
break;
default:
continue;
}
result = game_model->add_object (new_item,x,y,0);
switch (result) {
case 0:
cout << "Item added to (" << (int)x << "," << (int)y
<< ")" << endl;
break;
case EOCC:
cout << "(" << (int)x << ","
<< (int)y << ") already occupied." << endl;
break;
case EOOB:
cout << "Out of bounds." << endl;
break;
default:
break;
}
}
}
/** This method gets the x-coordinate for the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return x-coordinate of kart
*
* NOTE: Will return 0 upon error (invalid kart number, kart not
* initialized, etc.
*/
uint8_t controller::get_x (uint8_t kart_num) {
return game_model->get_x (kart_num);
}
/** This method gets the y-coordinate for the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return y-coordinate of kart
*
* NOTE: Will return 0 upon error (invalid kart number, kart not
* initialized, etc.
*/
uint8_t controller::get_y (uint8_t kart_num) {
return game_model->get_y (kart_num);
}
/** This method gets the speed of the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Speed of kart
*
* NOTE: Will return 0 upon error (invalid kart number, kart not
* initialized, etc.
*/
uint8_t controller::get_speed (uint8_t kart_num) {
return game_model->get_speed (kart_num);
}
/** This method gets the heading of the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Heading of kart
*
* NOTE: Will return 0 upon error (invalid kart number, kart not
* initialized, etc.
*/
uint16_t controller::get_heading (uint8_t kart_num) {
return game_model->get_heading (kart_num);
}
/** This method gets the current item of the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Current item
*
* NOTE: Will return NO_ITEM upon error (invalid kart number, kart not
* initialized, etc.
*/
item_values_t controller::get_item (uint8_t kart_num) {
return game_model->get_item (kart_num);
}
/** This method gets the current shield type of the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Current shield
*
* NOTE: Will return NO_ITEM upon error (invalid kart number, kart not
* initialized, etc.
*/
item_values_t controller::get_shield_type (uint8_t kart_num) {
return game_model->get_shield_type (kart_num);
}
/** This method gets the current shield count of the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Current shield count
*/
uint8_t controller::get_shield_count (uint8_t kart_num) {
return game_model->get_shield_count (kart_num);
}
/** This method gets the current lap number for the corresponding kart.
*
* @param kart_num Number of kart (1, 2, ...)
* @return Current lap
*/
uint8_t controller::get_cur_lap (uint8_t kart_num) {
return game_model->get_lap (kart_num);
}
#ifdef USE_GUI
/** This method prints the occupancy grid onto the GUI.
*/
void controller::gui_occupancy (void) {
Mat overlay, resized, *new_view;
Vec3b *over_row, *main_row;
char kart_label[2];
int i,x,y,x_start,y_start;
gridObject *cur_item;
// Clear display
overlay = imread ("./hudImages/star.jpg");
new_view = new Mat (600,600,CV_8UC3);
//resize (overlay, *new_view, Size (600,600), 0, 0);
*new_view = Scalar (255,255,255);
//
view = Mat::ones (600, 600, CV_8UC3);
// Label each kart
for (i=0;i<num_karts;i++) {
x = get_x (i+1) * 600 / game_grid->get_cols ();
y = get_y (i+1) * 600 / game_grid->get_rows ();
kart_label[0] = i+1+'0';
//
putText (view, kart_label, Point (x,y), FONT_HERSHEY_PLAIN, 2,
//
Scalar (255,255,255),3);
putText (*new_view, kart_label, Point (x,y), FONT_HERSHEY_PLAIN, 2,
Scalar (0,0,0),3);
}
}
// Add each item
for (i=0;i<game_grid->listLength ();i++) {
cur_item = game_grid->getFromList (i);
switch (cur_item->getType ()) {
case REDSHELL:
overlay = imread ("./hudImages/redshell.jpg");
break;
case BANANA:
overlay = imread ("./hudImages/banana.jpg");
break;
case GREENSHELL:
overlay = imread ("./hudImages/greenshell.jpg");
break;
case STAR:
overlay = imread ("./hudImages/star.jpg");
break;
case REDSHIELD3:
case REDSHIELD2:
case REDSHIELD1:
case MRSHELL:
overlay = imread ("./hudImages/multipleredshell.jpg");
break;
case ITEMBOX:
overlay = imread ("./hudImages/itembox.jpg");
break;
case NO_ITEM:
default:
continue;
}
resize (overlay, resized, Size(), 0.05, 0.05);
overlay.release ();
x_start = cur_item->getX () * 600 / game_grid->get_cols ();
y_start = cur_item->getY () * 600 / game_grid->get_rows ();
for (y=0;y<resized.rows;y++) {
if (y+y_start < new_view->rows) {
over_row = resized.ptr<Vec3b>(y);
main_row = new_view->ptr<Vec3b>(y+y_start);
for (x=0;x<resized.cols;x++) {
if (x+x_start < new_view->cols &&
(over_row[x][0] != 255 || over_row[x][1] != 255 ||
over_row[x][2] != 255)) {
main_row[x+x_start] = over_row[x];
}
}
}
}
resized.release ();
}
view = *new_view;
/** This function is used to determine the location of mouse clicks on the
* GUI window.
*
* @param event Mouse event, one of CV_EVENT_*
* @param x x-coordinate of mouse pointer
* @param y y-coordinate of mouse pointer
* @param flags Combination of CV_EVENT_FLAG_*
* @param param Parameter for callback function
*/
void mouse_handler (int event, int x, int y, int flags, void *param) {
if (event == CV_EVENT_LBUTTONDOWN ||
event == CV_EVENT_LBUTTONUP) {
mouse_input_t *input = (mouse_input_t *)param;
input->x = x;
input->y = y;
input->waiting = false;
}
}
#endif
iii.
grid.cpp
/** @title grid.cpp
* @description A grid to track objects for the game. I suck at descriptions.
* @author David Allender
* @author Joryl Calizo
* @date February 8, 2011
*/
#include "grid.h"
/** This constructor creates a new grid to store grid objects. The grid
* begins as a 2D pointer array with all boundary coordinates set to walls.
*
* @param cols Number of columns
* @param rows Number of rows
*/
grid::grid (uint8_t cols, uint8_t rows) {
// Initialize dimensions
num_cols = cols;
num_rows = rows;
num_objs = num_cols*num_rows;
// Create array
objectGrid = new gridObject*[num_objs];
// Begin with empty linked list
objectList = new linked_list();
// Begin with NULL pointers
for (int i=0;i<num_objs;i++) {
objectGrid[i] = NULL;
}
// Add walls at outermost coordinates
for (int i=0;i<num_rows;i++) {
addObject (new wallObject (0,i));
addObject (new wallObject (num_cols-1,i));
}
for (int j=0;j<num_cols;j++) {
addObject (new wallObject (j,0));
addObject (new wallObject (j,num_rows-1));
}
//
}
// Reset object list (outer walls do not belong in list)
objectList->clear_list ();
objectList = new linked_list();
/** This method checks if the coordinates inputted by the user are valid. This
* method is used prior to accessing the grid array to prevent segfaults and
* indexing incorrect addresses.
*
* @param x x-coordinate
* @param y y-coordinate
* @return False if coordinates are out-of-bounds
*/
bool grid::is_valid (uint8_t x, uint8_t y) {
if (x > num_cols) {
return false;
}
if (y > num_rows) {
return false;
}
return true;
}
/** This method adds a new object to the grid, if possible.
*
* @param obj New object to be added to grid
* @return False if object cannot be added or coordinates are already occupied
*/
bool grid::addObject (gridObject* obj) {
// Return false if object is NULL
if (!obj) {
return false;
}
// Get coordinates
uint8_t x = obj->getX();
uint8_t y = obj->getY();
// Return false if object is out-of-bounds
if (!is_valid(x,y)) {
return false;
}
// Return false if coordinates are already occupied
if (objectGrid[(num_cols * y + x)] != NULL) {
return false;
}
// Assign object to location
objectGrid[(num_cols * y + x)] = obj;
}
// Add object to linked list
objectList->add_to_tail (obj);
return true;
/** This method moves an object to a new location on the grid, if possible.
*
* @param obj Object to be moved
* @param x New x-coordinate
* @param y New y-coordinate
* @return False if object does not exist, is a wall, or is trying to move
*
to occupied coordinates
*/
bool grid::moveObject (gridObject* obj, uint8_t x, uint8_t y) {
// Return false if object is NULL
if (!obj) {
return false;
}
// Get coordinates
uint8_t x_old = obj->getX();
uint8_t y_old = obj->getY();
// Return false if coordinates are the same
if (x_old == x && y_old == y) {
return false;
}
// Return false if old or new coordinates are out-of-bounds
if (!is_valid(x,y) || !is_valid(x_old,y_old)) {
return false;
}
// Return false if coordinates are already occupied
if (objectGrid[(num_cols * y + x)] != NULL) {
return false;
}
// Assign object to new location
obj->setCoord (x,y);
objectGrid[(num_cols * y + x)] = obj;
objectGrid[(num_cols * y_old + x_old)] = NULL;
}
return true;
/** This method allows access to a one-dimensional array using both an x and
* y-coordinate.
*
* @param x x-coordinate
* @param y y-coordinate
* @return Object at given coordinates
*/
gridObject* grid::get (uint8_t x, uint8_t y) {
return objectGrid[(num_cols * y + x)];
}
/** This method gets a grid object from the linked list.
*
* @param index Index of object in linked list
* @return Object at index
*/
gridObject* grid::getFromList (uint8_t index) {
if (index >= objectList->get_length()) { // invalid index
return NULL;
}
return objectList->get(index);
}
/** This method removes an object pointer from the grid, if possible.
*
* @param obj Object to be removed from grid
* @return False if object does not exist (or is a wall)
*/
bool grid::removeObject (gridObject* obj) {
if (!obj) {
return false;
}
// Get coordinates
uint8_t x = obj->getX();
uint8_t y = obj->getY();
// Return false if coordinates are out-of-bounds
if (!is_valid(x,y)) {
return false;
}
//
}
if (obj->sameObject(objectGrid[(num_cols * y + x)])) {
delete objectGrid[(num_cols * y + x)];
objectGrid[(num_cols * y + x)] = NULL;
objectList->remove_node (obj);
return true;
}
cout << obj->getTypeString() << " cannot be removed." << endl;
cout << "Located at (" << x << "," << y << ")" << endl;
if (isOccupied (x,y)) {
cout << "(" << x << "," << y << ") is not occupied." << endl;
}
return false;
/** This method displays a list of all objects in the linked list.
*/
void grid::printList (void) {
for (int i=0;i<objectList->get_length();i++) {
cout << objectList->get(i) << endl;
}
}
/** This method checks if a location is occupied.
*
* @param x_coord x-coordinate
* @param y_coord y-coordinate
* @return True if location is occupied
*/
bool grid::isOccupied (uint8_t x, uint8_t y) {
if (objectGrid[(num_cols * y + x)] != NULL) {
return true;
}
return false;
}
/** This method returns the number of objects in the linked list.
*
* @return Number of objects in list
*/
uint8_t grid::listLength (void) {
return objectList->get_length();
}
/** This method displays a text-based representation of the grid. An 'x'
* corresponds to a wall, while a '.' corresponds to an unoccupied
* location. Any other grid object is labeled by its ID number.
*/
void grid::displayOccupancy (void) {
}
gridObject* cur;
for (int i=0;i<num_rows;i++) {
for (int j=0;j<num_cols;j++) {
cur = objectGrid[(num_cols * i + j)];
if (dynamic_cast<wallObject*> (cur)) {
cout << setw(4) << "x";
}
else if (cur == NULL) {
cout << setw(4) << ".";
}
else {
cout << setw(4) << (int)cur->getID();
}
}
cout << endl;
}
/** This method displays a text-based representation of the grid. An 'x'
* corresponds to a wall, while a '.' corresponds to an unoccupied
* location. Any other grid object is labeled by its ID number.
*
* @param scale Scaling factor used when printing to cout (0 to 100%)
*/
void grid::displayOccupancy (uint8_t scale) {
gridObject* cur;
uint8_t dx, dy;
bool found_first;
if (scale >= 100) { // saturate at 100%, call normal method
displayOccupancy();
}
else if (scale){
dx = num_cols / scale;
dy = num_rows / scale;
for (int i=0;i<num_rows-dy;i+=dy) {
for (int j=0;j<num_cols-dx;j+=dx) {
found_first = false;
for (int k=i;k<i+dy;k++) {
for (int l=j;l<j+dx;l++) {
if (objectGrid[num_cols * k + l] != NULL &&
!found_first) {
cur = objectGrid[num_cols * k + l];
found_first = true;
break;
}
}
if (found_first) {
break;
}
}
if (!found_first) {
cout << setw(4) << ".";
}
else if (dynamic_cast<wallObject*> (cur)) {
cout << setw(4) << "x";
}
else {
cout << setw(4) << (int)cur->getID();
}
}
cout << setw(4) << "x" << endl;
}
for (int i=0;i<num_rows;i+=dy) {
cout << setw(4) << "x";
}
}
}
cout << endl;
/** This method returns the number of columns.
*
* @return Number of columns
*/
unsigned int grid::get_cols (void) {
return num_cols;
}
/** This method returns the number of rows.
*
* @return Number of rows
*/
unsigned int grid::get_rows (void) {
return num_rows;
}
ostream& operator<<(ostream& out, gridObject* my_obj) {
if (!my_obj) {
return out;
}
// Print generic grid object stuff
out << "Object " << my_obj->getID() << ": " << my_obj->getTypeString()
<< endl;
out << "Coordinates: (" << my_obj->getX() << ", "
<< my_obj->getY() << ")" << endl;
out << "Size: " << my_obj->getSize() << endl;
out << "Strength: " << my_obj->getStrength() << endl;
// Print mobile grid object stuff
if (dynamic_cast<mobileGridObject*> (my_obj)) {
out << "Speed: " << my_obj->getSpeed() << endl;
out << "Heading: " << my_obj->getDirection() << endl;
}
// Print kart object stuff
if (dynamic_cast<kartObject*> (my_obj)) {
out << "Item: " << my_obj->getItemString() << endl;
out << "Shield: ";
if (!my_obj->hasShield()) {
out << "None." << endl;
}
else {
out << my_obj->getShieldType();
if (my_obj->getShieldType() != STAR) {
out << " x" << my_obj->getShieldCount() << endl;
}
}
}
out << endl;
return out;
}
ostream& operator<<(ostream& out, uint8_t x) {
out << (int)x;
return out;
}
ostream& operator<<(ostream& out, int8_t x) {
out << (int)x;
return out;
}
ostream& operator<<(ostream& out, item_values_t item) {
switch (item) {
case NO_ITEM:
out << "No Item";
break;
case KART:
out << "Kart";
break;
case GREENSHELL:
out << "Green Shell";
break;
case REDSHELL:
out << "Red Shell";
break;
case GREENSHIELD3:
out << "Green Shell x3";
break;
case GREENSHIELD2:
out << "Green Shell x2";
break;
case GREENSHIELD1:
out << "Green Shell x1";
break;
case REDSHIELD3:
out << "Red Shell x3";
break;
case REDSHIELD2:
out << "Red Shell x2";
break;
case REDSHIELD1:
out << "Red Shell x1";
break;
case BANANA:
out << "Banana";
break;
case MUSHROOM:
out << "Mushroom";
break;
case STAR:
out << "Star";
break;
case ITEMBOX:
out << "Item Box";
break;
case WALL:
out << "Wall";
break;
default:
out << "Oh shit (" << (int)item << ").";
break;
}
return out;
}
iv.
gridObject.cpp
/** @title gridObject.cpp
* @description Object to be located on a grid. Obviously.
* @author David Allender
* @author Joryl Calizo
* @date February 1, 2011
*/
#include "gridObject.h"
/** This constructor creates a new generic grid object. The size of the object
* is represented as a radius and centered at the specified x,y-coordinates.
*
* @param x_coord x-coordinate
* @param y_coord y-coordinate
* @param radius Size of object
* @param strength Strength of object
* @param type Object type
*/
gridObject::gridObject (uint8_t x_coord, uint8_t y_coord, uint8_t radius,
uint8_t strength, item_values_t type) {
static uint8_t count = 0; // number of unique grid objects
x = x_coord;
y = y_coord;
size = radius;
this->strength = strength;
ID = ++count;
// generates a new ID everytime
this->type = type;
}
/** This method returns the x and y coordinates of the object as a combined
* 16-bit integer. This can be used directly in the data packet sent to each
* go-kart. The higher-order bits represent the x-coordinate, while the lower* order bits represent the y-coordinate.
*
* @return Object coordinates
*/
uint16_t gridObject::getCoords(void) {
return (x << 8) + y;
}
/** This method returns the x-coordinate only.
*
* @return x-coordinate
*/
uint8_t gridObject::getX(void) {
return x;
}
/** This method returns the y-coordinate only.
*
* @return y-coordinate
*/
uint8_t gridObject::getY(void) {
return y;
}
/** This method returns the size of the object.
*
* @return Object size
*/
uint8_t gridObject::getSize(void) {
return size;
}
/** This method returns the strength of the object.
*
* @return Object strength
*/
uint8_t gridObject::getStrength(void) {
return strength;
}
/** This method returns the unique ID of the object.
*
* @return Object ID
*/
uint16_t gridObject::getID(void) {
return ID;
}
/** This method returns the object type.
*
* @return Object type
*/
item_values_t gridObject::getType(void) {
return type;
}
/** This method returns the string representation of the object type.
*
* @return String representation of object type
*/
const char* gridObject::getTypeString(void) {
switch (type) {
case NO_ITEM:
return "No Item";
case KART:
return "Kart";
case GREENSHELL:
return "Green Shell";
case REDSHELL:
return "Red Shell";
case GREENSHIELD3:
return "Green Shell x3";
case GREENSHIELD2:
return "Green Shell x2";
case GREENSHIELD1:
return "Green Shell x1";
case REDSHIELD3:
return "Red Shell x3";
case REDSHIELD2:
return "Red Shell x2";
case REDSHIELD1:
return "Red Shell x1";
case BANANA:
return "Banana";
case MUSHROOM:
return "Mushroom";
case STAR:
}
}
return "Star";
case ITEMBOX:
return "Item Box";
case WALL:
return "Wall";
default:
return "Oh shit.";
/** This method changes the object's coordinates. Currently, there is no reason
* to return "false," but I'm sure we'll think of something.
*
* @param x_coord New x-coordinate
* @param y_coord New y-coordinate
* @return False if coordinates cannot be changed
*/
bool gridObject::setCoord(uint8_t x_coord, uint8_t y_coord) {
x = x_coord;
y = y_coord;
return true;
}
/** This method changes the object's size. I guess this can be used for
* animation purposes? Always returns true (for now).
*
* @param size New object size
* @return False if size cannot be changed
*/
bool gridObject::setSize(uint8_t size) {
this->size = size;
return true;
}
/** This method changes the object's strength. Always returns true (for now).
*
* @param strength New object strength
* @return False if strength cannot be changed
*/
bool gridObject::setStrength(uint8_t strength) {
this->strength = strength;
return true;
}
/** This method changes the object ID. Always returns true (for now).
*
* @param id New object ID
* @return False if ID cannot be changed
*/
bool gridObject::setID(uint16_t ID) {
this->ID = ID;
return true;
}
/** This method returns the distance between this object and another object
* on the grid.
*
* @param other Pointer to second object
* @return Distance from second object
*/
uint16_t gridObject::getDistance(gridObject* other) {
}
int16_t x_diff = other->x - this->x;
int16_t y_diff = this->y - other->y;
return sqrt(x_diff*x_diff + y_diff*y_diff);
/** This method returns the angle towards a second object on the grid.
*
* @param other Pointer to second object
* @return Angle to second object
*/
uint16_t gridObject::getAngle(gridObject* other) {
int16_t heading = atan2 (this->y-other->y,other->x-this->x) * 180 / PI;
if (heading < 0) {
heading = heading + 360;
}
return (uint16_t)heading;
}
/** This method checks to see if the second object is actually the same (to
* avoid false collisions).
*
* @param object Pointer to second object
* @return True if objects are the same
*/
bool gridObject::sameObject(gridObject* other) {
return other == this;
}
/* Virtual methods */
uint8_t gridObject::getSpeed(void) {
return 0;
}
uint16_t gridObject::getDirection(void) {
return 0;
}
bool gridObject::setSpeed(uint8_t speed) {
return false;
}
bool gridObject::setDirection(uint16_t heading) {
return false;
}
item_values_t gridObject::getItem(void) {
return NO_ITEM;
}
const char* gridObject::getItemString(void) {
return "I'm not a kart.";
}
gridObject* gridObject::useItem() {
return NULL;
}
bool gridObject::setItem(item_values_t item) {
return false;
}
bool gridObject::starPowerStart(void) {
return false;
}
bool gridObject::starPowerStop(void) {
return false;
}
bool gridObject::setTargetCoord (uint8_t x_coord, uint8_t y_coord) {
return false;
}
bool gridObject::setTargetObject(gridObject* target) {
return false;
}
bool gridObject::hasShield (void) {
return false;
}
item_values_t gridObject::getShieldType(void) {
return NO_ITEM;
}
uint8_t gridObject::getShieldCount(void) {
return 0;
}
bool gridObject::setShield(item_values_t shield_type) {
return false;
}
bool gridObject::useShield(void) {
return false;
}
//uint16_t gridObject::getRelativeAngle (gridObject* other) {
// return 0;
//}
int16_t gridObject::getRelativeX (gridObject* other) {
return 0;
}
int16_t gridObject::getRelativeY (gridObject* other) {
return 0;
}
v.
kartObject.cpp
/** @title kartObject.cpp
* @description A go-kart grid object. Descendant of the mobileGridObject
*
class.
* @author David Allender
* @author Joryl Calizo
* @date February 7, 2011
*/
#include "kartObject.h"
/** This constructor creates a new go-kart object. Aside from the standard
* mobile object parameters, the go-kart can also start with an item.
*
* @param x_coord x-coordinate
* @param y_coord y-coordinate
* @param heading Compass direction of go-kart
* @param item Item held (0x00 for no item)
*/
kartObject::kartObject (uint8_t x_coord, uint8_t y_coord, uint16_t heading,
item_values_t item)
:mobileGridObject(x_coord, y_coord, KART_SIZE, KART_STRENGTH, 0, heading,
KART) {
this->item = item;
shield.item = NO_ITEM;
shield.num = 0;
}
/** This method returns the value of the current item held by the go-kart. See
* items.h for item values.
*
* @return Item held
*/
item_values_t kartObject::getItem(void) {
return item;
}
/** This method returns the string represenation of the current item held by
* the go-kart.
*
* @return String represenation of item held
*/
const char* kartObject::getItemString(void) {
switch (item) {
case NO_ITEM:
return "No Item";
case KART:
return "Kart";
case GREENSHELL:
return "Green Shell";
case REDSHELL:
return "Red Shell";
case GREENSHIELD3:
return "Green Shell x3";
case GREENSHIELD2:
return "Green Shell x2";
case GREENSHIELD1:
return "Green Shell x1";
case REDSHIELD3:
return "Red Shell x3";
case REDSHIELD2:
return "Red Shell x2";
case REDSHIELD1:
return "Red Shell x1";
case BANANA:
return "Banana";
case MUSHROOM:
return "Mushroom";
case STAR:
return "Star";
default:
return "Oh shit.";
}
}
/** This method "uses" the item held by the go-kart. After calling this method,
* the go-kart is unarmed. See items.h for item values.
*
* @return Pointer to new grid object if one is created
*/
gridObject* kartObject::useItem() {
gridObject* p_new; // Create pointer to new object
// Determine location of new object
int16_t x_new;
int16_t y_new;
if (hasShield()) { // Kart has shield, use first
switch (shield.item) {
case GREENSHELL:
case REDSHELL:
x_new = round(x+DX*cos(heading*PI/180));
y_new = round(y-DY*sin(heading*PI/180));
}
}
if (x_new < 0) { // Boundary conditions
x_new = 0;
}
if (y_new < 0) {
y_new = 0;
}
if (shield.item == GREENSHELL) {
p_new = new greenShellObject (x_new,y_new,heading);
}
else if (shield.item == REDSHELL) {
p_new = new redShellObject (x_new,y_new,heading,NULL);
}
useShield();
return p_new;
case STAR: // Continue if current item is not shield
switch (item) {
case GREENSHIELD3:
case REDSHIELD3:
case STAR:
return NULL;
default:
break;
}
break; // if this point is reached, item is ok to use
default: // invalid shield, how'd you get here?
return NULL;
if (item == NO_ITEM) { // Kart has no item
return NULL;
}
switch (item) {
case GREENSHELL:
case REDSHELL:
x_new = round(x+DX*cos(heading*PI/180));
y_new = round(y-DY*sin(heading*PI/180));
if (x_new < 0) {
// Boundary conditions
}
}
x_new = 0;
}
if (y_new < 0) {
y_new = 0;
}
if (item == GREENSHELL) {
p_new = new greenShellObject (x_new,y_new,heading);
}
else if (item == REDSHELL) {
p_new = new redShellObject (x_new,y_new,heading,NULL);
}
item = NO_ITEM;
return p_new;
case BANANA:
x_new = round(x-DX*cos(heading*PI/180));
y_new = round(y+DY*sin(heading*PI/180));
if (x_new < 0) { // Boundary conditions
x_new = 0;
}
if (y_new < 0) {
y_new = 0;
}
p_new = new bananaObject (x_new,y_new);
item = NO_ITEM;
return p_new;
case GREENSHIELD3:
setShield(GREENSHELL);
item = NO_ITEM;
return NULL;
case REDSHIELD3:
setShield(REDSHELL);
item = NO_ITEM;
return NULL;
case MUSHROOM:
item = NO_ITEM;
return NULL;
case STAR:
starPowerStart();
return NULL;
default: // Invalid value
return NULL;
/** This method gives the go-kart a new item, if possible. See items.h for item
* values.
*
* @param item New item
* @return False if go-kart already has an item
*/
bool kartObject::setItem(item_values_t item) {
if (this->item) {
return false;
}
this->item = item;
return true;
}
/** This method tells the go-kart to take steroids, raising its strength.
* After calling this method, the go-kart is unarmed. See items.h for item
* values.
* NOTE: A thread must be created to track length of star power.
*
* @return False if go-kart does not have a star
*/
bool kartObject::starPowerStart(void) {
if (item != STAR) {
return false;
}
item = NO_ITEM;
strength = STAR_STRENGTH;
shield.item = STAR;
shield.num = STAR;
return true;
}
/** This method tells the go-kart that steroids are unhealthy. The go-kart is
* returned to base strength.
* NOTE: Will be called by a terminating thread when star power is exhausted.
*
* @return False if go-kart already stopped taking steroids
*/
bool kartObject::starPowerStop(void) {
if (shield.item != STAR) {
return false;
}
strength = KART_STRENGTH;
shield.item = NO_ITEM;
shield.num = 0;
return true;
}
/** This method checks whether or not the go-kart has a shield.
*
* @return True if go-kart has a shield
*/
bool kartObject::hasShield(void) {
if (shield.num) {
return true;
}
return false;
}
/** This method returns the shield type. If the go-kart has no shield, this
* method returns NO_ITEM.
*
* @return Shield type
*/
item_values_t kartObject::getShieldType(void) {
return shield.item;
}
/** This method returns the number of shields protecting the go-kart.
*
* @return Number of shields
*/
uint8_t kartObject::getShieldCount(void) {
return shield.num;
}
/** This method gives the go-kart a shield if it does not currently have one.
*
* @param shield_type Shield type
* @return False if already has a shield or shield type is invalid
*/
bool kartObject::setShield(item_values_t shield_type) {
if (hasShield()) {
return false;
}
switch (shield_type) {
case STAR:
shield.item = STAR;
shield.num = STAR;
break;
case GREENSHELL:
case GREENSHIELD3: // just in case
shield.item = GREENSHELL;
shield.num = 3;
break;
case REDSHELL:
case REDSHIELD3:
shield.item = REDSHELL;
shield.num = 3;
break;
default: // invalid type
return false;
}
return true;
}
/** This method uses one of the go-kart's shields. Go-karts with stars remain
* unchanged.
*
* @return False if go-kart does not have a shield
*/
bool kartObject::useShield(void) {
switch (shield.num) {
case 1: // no more shield
shield.item = NO_ITEM;
case 2:
case 3:
shield.num--;
case STAR: // do nothing for stars
return true;
default:
return false;
}
}
/** This method returns the angle towards a second object on the grid
* relative to the kart's position and heading.
*
* @param other Pointer to second object
* @return Relative angle to second object
*/
uint16_t kartObject::getRelativeAngle (gridObject* other) {
if (sameObject(other)) {
return 0;
}
int16_t angle = 90 - (heading - getAngle (other));
if (angle < 0) {
angle += 360;
}
}
if (angle >= 360) {
angle -= 360;
}
return (uint16_t)angle;
/** This method returns the x-coordinate of a second object on the grid
* relative to the kart's position and heading.
*
* @param other Pointer to second object
* @return Relative x-coordinate to second object
*/
int16_t kartObject::getRelativeX (gridObject* other) {
if (sameObject(other)) {
return 0;
}
return round(getDistance(other) * cos(getRelativeAngle(other)*PI/180));
}
/** This method returns the y-coordinate of a second object on the grid
* relative to the kart's position and heading.
*
* @param other Pointer to second object
* @return Relative y-coordinate to second object
*/
int16_t kartObject::getRelativeY (gridObject* other) {
if (sameObject(other)) {
return 0;
}
return round(getDistance(other) * sin(getRelativeAngle(other)*PI/180));
}
vi.
networks.cpp
/** @title networks.cpp
* @brief This is the networks file, where all of the inter-device communication happens
* @details This file represents the model for a model-view-controller (MVC)
*
architecture. Public methods are called by the controller when changes
*
to the game state need to be made. Other query methods are used by the
*
view to update local game state data displayed on the user interface.
* @author David Allender
* @author Joryl Calizo
* @date April 10, 2011
*/
#include "networks.h"
using namespace std;
/** This constructor creates a new networking object.
*
* @param type The type of network object(1 for KART, 2 for SERVER)
* @param player This is the player number(port changes according to this number)
*/
networking::networking (int type, int player) {
socket_addrinfo = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
len = sizeof(*socket_addrinfo);
if (type == NETWORK_KART) {
kart_setup(player);
}
else if (type == NETWORK_SERVER){
server_setup(player);
//connect();
}
}
else{
cerr << "ERROR: Wrong type for network object(1 for Kart, 2 for Server" << endl;
}
/** This is the networking setup code for the server
*
* @param player This is the player number you wish to connect to
*/
void networking::server_setup(int player){
if((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0){
perror("Socket Call");
exit(-1);
}
memset(socket_addrinfo, 0, sizeof(*socket_addrinfo));
//set up the sock
socket_addrinfo->sin_family = AF_INET;
socket_addrinfo->sin_addr.s_addr = htonl(INADDR_ANY);
//choose what port to use
socket_addrinfo->sin_port = htons(PORTNUMBER + (player - 1));
//bind the name (address) to a port
if(bind(sock,(struct sockaddr*)socket_addrinfo, sizeof(*socket_addrinfo)) < 0){
perror("bind call");
exit(-1);
}
}
if(getsockname(sock,(struct sockaddr*)socket_addrinfo,&len) < 0){
perror("setsockname call");
exit(-1);
}
printf("Player %d has port %d \n", player, ntohs(socket_addrinfo->sin_port));
/** This is the networking setup code for the kart
*
* @param player This is the Player Number
*/
void networking::kart_setup(int kartnum){
struct hostent *hp;
if((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0){
perror("Socket Call");
exit(-1);
}
socket_addrinfo->sin_family = AF_INET;
//What port do you want to connect to?
socket_addrinfo->sin_port = htons(PORTNUMBER + (kartnum - 1));
//s_addrinfo.sin_addr.s_addr = htonl(INADDR_ANY);
//The address below is the IP address of the SERVER, the
//kart uses this to connect
hp = gethostbyname("192.168.1.101");
//memcpy(&s_addrinfo.sin_addr, hp.h_addr, hp.h_length);
bcopy(hp->h_addr, &(socket_addrinfo->sin_addr.s_addr), hp->h_length);
}
/** This method sends data, be it from kart to server or server to kart, it doesn't matter
*
* @param player This is the buffer if information to send
* @return the number of bytes actually sent(used for debug)
*/
int networking::send_data(char *buffer){
return sendto(sock, buffer, strlen(buffer), 0,
(struct sockaddr *)socket_addrinfo, sizeof(*socket_addrinfo));
}
/** This method sends data, be it from kart to server or server to kart, it doesn't matter
*
* @param player This is the buffer if information to recieve from(max recv length is 150)
* @return the number of bytes actually received(used for debug)
*/
int networking::recv_data(char *buffer){
return recvfrom(sock, buffer, MAX_PACKET_SIZE, 0,
(struct sockaddr *)socket_addrinfo, &len);
}
/**
* This method gets called on the server end, and waits for a packet to arrive from the kart
*/
void networking::connect(){
char* inpacket = (char*)malloc(50);
recvfrom(sock, inpacket, 50, 0,
(struct sockaddr *)socket_addrinfo, &len);
cout << "Kart connected: " << endl;
memset(inpacket, 0, 50);
strcpy(inpacket, "Connected to server!");
send_data (inpacket);
free(inpacket);
}
vii.
kartHud.cpp
#include "kartHud.h"
#include "items.h"
/**************public functions***************/
/*
* constructor
*/
void kartHud::init(int pos, int totalLap, int cam)
{
hud::init(cam);
this->pos = pos;
wep = NO_ITEM;
currLap = 0;
this->totalLap = totalLap;
raceTime = 0;
}
int kartHud::getWep()
{
return wep;
}
int kartHud::getPos()
{
return pos;
}
int kartHud::getLap()
{
return currLap;
}
/*
* function used to change huds status
* pos - pos is change if not -1
* currlap - changed if not -1
* wep - huds weapon is set to this weapon if it is not -1
* totallap - huds totallap is changed if it is not -1;
* elapsedtime - huds elapsed time is change if it not set to -1
*/
void kartHud::changeHud(int pos, int currLap, int wep, int totalLap)
{
if(pos!= -1)
{
this->pos = pos;
}
if(currLap != -1)
{
this->currLap = currLap;
}
if(wep != -1)
{
this->wep = wep;
//wepBuff = wep;
}
if(totalLap != -1)
{
this->totalLap = totalLap;
}
}
void kartHud::setRaceTime(int time)
{
raceTime = time;
}
/*************private functions**************/
void kartHud::updateFrame()
{
placePos(currFrame, pos);
placeWep();
placeTime((raceTime/60000)%60, (raceTime/1000) %60,(raceTime%1000));
}
placeLap(currFrame, currLap, totalLap);
/*
* shows what weapon a person has on the HUD
*/
bool kartHud::placeWep()
{
IplImage *original = NULL;
if(wep == MRSHELL)
{
original = cvLoadImage("./hudImages/multipleredshell.jpg");
}else if(wep == REDSHELL)
{
original = cvLoadImage("./hudImages/redshell.jpg");
}else if(wep == BANANA)
{
original = cvLoadImage("./hudImages/banana.jpg");
}else if(wep == MUSHROOM)
{
original = cvLoadImage("./hudImages/mushroom.jpg");
}else if(wep == STAR)
{
original = cvLoadImage("./hudImages/star.jpg");
}else if(wep == GREENSHELL)
{
original = cvLoadImage("./hudImages/greenshell.jpg");
}else if(wep == MGSHELL)
{
original = cvLoadImage("./hudImages/multiplegreenshell.jpg");
}
if(!original)
{
return false;
}
cvFlip(original);
placeImage(currFrame, original, 20, 5, 70);
}
cvReleaseImage(&original);
return true;
/* function is to place two images on top of each other
* the background image is the anchor image and if the two resolutions
* are not the same then the foreground will be set to the background
* image resolution.
*Args: background- background image
*
foreground - foreground image
*
shrink - percent of size the foreground image should change too
*
xoffset - percent of offset in x the foreground image should be moved too
*
yoffset - percent of offset in y the foreground image should be moved too
*/
void kartHud::placeImage(IplImage *background, IplImage *foreground,
double size, double xoffset, double yoffset)
{
IplImage * newsize;
int i, j;
int offseti, offsetj;
CvScalar pixel;
offseti = (int)((background->height * yoffset)/100);
offsetj = (int)((background->width * xoffset)/100);
newsize = cvCreateImage(cvSize((int)((background->width*size)/100), (int)((background->width*size)/100)),
background->depth, foreground->nChannels);
cvResize(foreground, newsize);
for(i = 0; i < newsize->height; i++)
{
for(j=0; j< newsize->width; j++)
{
if(i+offseti < background->height &&
j+offsetj < background->width)
{
pixel = cvGet2D(newsize, i, j);
cvSet2D(background, i+offseti, j+offsetj, pixel);
}
}
}
}
cvReleaseImage(&newsize);
/*
* function the places a person position on the HUD
*/
void kartHud::placePos(IplImage *frame, int pos)
{
int i, j;
IplImage *original;
IplImage * place;
if(pos == 1)
{
original = cvLoadImage("./hudImages/1st.jpg");
}else
{
original = cvLoadImage("./hudImages/2nd.jpg");
}
if(!original)
{
return;
}
place = cvCreateImage(cvSize((int)(frame->width *.20), (int)(frame->height*.20)), frame->depth, original->nChannels);
cvResize(original, place);
cvFlip(place, place, 0);
for(i = (place->height - 1); i >= 0; i--)
{
for(j=0; j< place->width; j++)
{
CvScalar v = cvGet2D(place, i, j);
if(v.val[0] < 200)
{
cvSet2D(frame, i,j, v);
}
}
}
cvReleaseImage(&place);
cvReleaseImage(&original);
}
void kartHud::placeTime(int minutes, int seconds, int milliseconds)
{
string time = "TIME";
string mins;
string secs;
string millis;
stringstream convInt;
CvScalar color;
color = cvScalar(40,238,204);
CvFont font;
double hScale=1.0;
double vScale=1.0;
int posx, posy;
int lineWidth= 3;
/*convert ints to string*/
convInt.str("");
convInt << minutes;
mins = convInt.str();
convInt.str("");
convInt << seconds;
secs= convInt.str();;
convInt.str("");
convInt << milliseconds;
millis= convInt.str();
if(milliseconds < 100)
{
millis = "0" + millis;
if(milliseconds < 10)
{
millis = "0" + millis;
}
}
if(seconds < 10)
{
secs = "0" + secs;
}
if(minutes < 10)
{
mins = "0" + mins;
}
posx = currFrame->width;
posx = (int)posx *.6;
posy = currFrame->height;
posy = (int)(posy * .07);
/*font initialization */
cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX|CV_FONT_ITALIC, hScale,vScale,0,lineWidth);
cvFlip(currFrame, 0);
//Time
cvPutText (currFrame, time.c_str(),cvPoint(posx,posy), &font, color);
posx = posx + (int)(currFrame->width*.125);
// 00:
cvPutText (currFrame, mins.c_str(),cvPoint(posx,posy), &font, color);
posx = posx + (int)(currFrame->width*.07);
cvPutText (currFrame, ":",cvPoint(posx,posy), &font, color);
// 00:
posx = posx + (int)(currFrame->width*.02);
cvPutText (currFrame, secs.c_str(),cvPoint(posx,posy), &font, color);
posx = posx + (int)(currFrame->width*.07);
cvPutText (currFrame, ":",cvPoint(posx,posy), &font, color);
// 000
posx = posx + (int)(currFrame->width*.02);
cvPutText (currFrame, millis.c_str(),cvPoint(posx,posy), &font, color);
cvFlip(currFrame, 0);
}
void kartHud::placeLap(IplImage *frame, int currlap, int totallap)
{
string lap = "LAP";
string curr;
string total;
stringstream convInt;
CvScalar color;
color = cvScalar(40,238,204);
CvFont font;
double hScale=1.0;
double vScale=1.0;
int posx, posy;
int lineWidth= 3;
convInt << currlap;
curr = convInt.str();
convInt.str("");
convInt << totallap;
total = convInt.str();
posx = frame->width;
posx = (int)posx *.7;
posy = frame->height;
posy = (int)(posy * .15);
/*font initialization */
cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX|CV_FONT_ITALIC, hScale,vScale,0,lineWidth);
cvFlip(frame, 0);
//lap
cvPutText (frame, lap.c_str(),cvPoint(posx,posy), &font, color);
posx = posx + (int)(frame->width*.12);
// 00:
cvPutText (frame, curr.c_str(),cvPoint(posx,posy), &font, color);
posx = posx + (int)(frame->width*.03);
cvPutText (frame, "/",cvPoint(posx,posy), &font, color);
// 00:
posx = posx + (int)(frame->width*.035);
cvPutText (frame, total.c_str(),cvPoint(posx,posy), &font, color);
cvFlip(frame, 0);
}
viii.
kartNetworks.cpp
/**
* CPE461 Winter 2011
* @author: David Allender
* @author: Joseph Abad
*
* This is to be used on the actual Go-Kart
*
* It's responsibilities include:
* *Getting raw coordinates from the Arduino
* *Sending the raw coordinates back to the main computer
* *Receiving the data and passing it to Joseph Abad
*/
#include "kartNetworks.h"
using namespace std;
void kartNetworks::init()
{
char *buffer;
buffer = (char *) malloc(sizeof(char *));
send_data(buffer);
}
void kartNetworks::step()
{
char *recPacket;
recv_data(recPacket);
}
void parsePacket(char *inpacket)
{
//cout << "sending initial packet" << endl;
char *tok;
int tokCount = 0;
int wep;
int shield;
int lap;
int place;
int type;
int speed;
int key = 0;
double x;
double y;
bool hasShield = false;
pthread_t gwepThread;
tok = strtok(inpacket, " ,");
while(tok)
{
//printf("parsing packet (%d): %s\n", tokCount, tok);
if(tokCount == 1)
{
//currentWeapon being held
wep = atoi(tok);
if(wep == NOWEP )
{
changeHud(-1, -1, NOWEP, -1, -1);
}else if(getWep() != wep)
{
setWeapon(wep);
pthread_create(&gwepThread, NULL, getWeapon, (void *) &wep);
}
}else if(tokCount == 2)
{
//current shield
hasShield = false;
shield = atoi(tok);
if(shield == STAR)
{
setStar(true);
}else if(shield == RSHELL)
{
hasShield = true;
}else
{
setStar(false);
}
}else if(tokCount == 3)
{
//shield count
if(hasShield)
{
setStar(false);
setShield(atoi(tok));
}else
{
setShield(0);
}
}else if(tokCount == 4)
{
//lap No.
lap = atoi(tok);
if(getLap() != lap)
{
changeHud(-1, atoi(tok), -1, -1, -1);
}
if(!raceStarted && lap == 1)
{
raceStarted = true;
startRace();
}
}else if(tokCount == 5)
{
//place
place = atoi(tok);
if(getPos() != place)
{
changeHud(atoi(tok), -1, -1, -1, -1);
}
}else if (tokCount == 6)
{
speed = atoi(tok);
if(speed == 100 && currSpeed != 100)
{
setSpeed("d");
setSpeed("d");
}else if(speed == 75 && currSpeed != 75)
{
setSpeed("K");
setSpeed("K");
}else if(speed == 50 && currSpeed != 50)
{
setSpeed("2");
setSpeed("2");
}else if(currSpeed != 0 && speed == 0)
{
setSpeed("0");
setSpeed("0");
pthread_create(&vibThread, NULL, vibrate, (void *)1);
}
currSpeed = speed;
}
else if(tokCount > 7)
{
type = atoi(tok);
tok = strtok(NULL, " ,");
tokCount++;
//printf("parsing packet (%d) in packet x: %s\n", tokCount, tok);
x = (double)atoi(tok);
tok = strtok(NULL, " ,");
tokCount++;
//printf("parsing packet (%d) in packet y: %s\n", tokCount, tok);
y = (double)atoi(tok);
if(type == NOWEP)
{
removeObj(key);
}else
{
updateObj(key, x/(10.0), y/(10.0), type);
}
key++;
}
}
tok = strtok(NULL, " ,");
tokCount++;
}
void fireButt()
{
wepUsed = true;
}
void *sendPackets(void *arg)
{
//char buffer[20] = "30,30,10,20,1";
//char buffer[40];
char *buffer = (char *) malloc(40);
while(1)
{
readData(buffer);
//buffer[strlen(buffer)-1] = '\0';
if(strlen(buffer) > 2)
{
if(wepUsed)
{
strcat(buffer, ",1");
wepUsed = false;
}
}
}else
{
strcat(buffer, ",0");
}
cout << strlen(buffer) << endl;
cout << "buffer: " << buffer << endl;
sendto(sock, buffer, 40, 0, (struct sockaddr *)&remote, sizeof(remote));
memset(buffer, 0, 40);
}
void receivePackets()
{
char *inpacket = (char*)malloc(150);
int received = 0;
uint32_t len = sizeof(remote);
}
while(1)
{
//Receive packet in 'inpacket' buffer
received = recvfrom(sock, inpacket, 150, 0, (struct sockaddr *)&remote, &len);
//Decode packets here
//cout << "Received: " << received << endl;
//cout << "Packet: " << inpacket << endl;
parsePacket(inpacket);
memset(inpacket, 0, 150);
//call necessary functions that use packet's data
}
ix.
kartObjLayer.cpp
#include "kartObjLayer.h"
/****************public functions***********/
//kartObjLayer::kartObjLayer():objLayer()
void kartObjLayer::init()
{
objLayer::init();
lightState = REDLIGHT;
showLight = false;
initObjects();
}
void kartObjLayer::setShield(int count, item_values_t type)
{
objects[MAXOBJECTS].x = 0;
objects[MAXOBJECTS].y = 0;
if(type == REDSHELL)
{
if(count == 3)
{
objects[MAXOBJECTS].type = REDSHIELD3;
}else if(count == 2)
{
objects[MAXOBJECTS].type = REDSHIELD2;
}else
{
objects[MAXOBJECTS].type = REDSHIELD1;
}
}else if(type == GREENSHELL)
{
if(count == 3)
{
objects[MAXOBJECTS].type = GREENSHIELD3;
}else if(count == 2)
{
objects[MAXOBJECTS].type = GREENSHIELD2;
}else
{
objects[MAXOBJECTS].type = GREENSHIELD1;
}
}else
{
objects[MAXOBJECTS].type = NO_ITEM;
}
}
/*************private functions************/
void kartObjLayer::updateFrame()
{
glPushMatrix();
if(showLight)
{
drawTrafficLight();
}
for(int i = 0; i < (MAXOBJECTS +1); i++)
{
if(objects[i].type == ITEMBOX)
{
drawWepBox(objects[i].x, objects[i].y);
}else if(objects[i].type == REDSHELL)
{
drawRShell(objects[i].x, objects[i].y);
}else if(objects[i].type == BANANA)
{
drawBanana(objects[i].x, objects[i].y);
}else if(objects[i].type == GREENSHELL)
{
drawGShell(objects[i].x, objects[i].y);
}else if(objects[i].type == GREENSHIELD3)
{
drawShield(objects[i].x, objects[i].y, 3, GREENSHELL);
}else if(objects[i].type == GREENSHIELD2)
{
drawShield(objects[i].x, objects[i].y, 2, GREENSHELL);
}else if(objects[i].type == GREENSHIELD1)
{
drawShield(objects[i].x, objects[i].y, 1, GREENSHELL);
}else if(objects[i].type == REDSHIELD3)
{
drawShield(objects[i].x, objects[i].y, 3, REDSHELL);
}else if(objects[i].type == REDSHIELD2)
{
drawShield(objects[i].x, objects[i].y, 2, REDSHELL);
}else if(objects[i].type == REDSHIELD1)
{
drawShield(objects[i].x, objects[i].y, 1, REDSHELL);
}
}
glPopMatrix();
}
/*
* initializes the objects to be empty so no 3d objects
* will be drawn on the screen
*/
void kartObjLayer::initObjects()
{
int i;
for(i = 0; i < MAXOBJECTS; i++)
{
objects[i].x = 0;
objects[i].y = 0;
objects[i].type = NO_ITEM;
}
initItems();
}
void kartObjLayer::initItems()
{
wepBox1 = glmReadOBJ("./layerObjects/wepBox1.obj");
if(!wepBox1)
{
printf("unable to load wepBox1.obj\n");
}
rshell = glmReadOBJ("./layerObjects/SHELL.obj");
if(!rshell)
{
printf("unable to load SHELL.obj\n");
}
gshell = glmReadOBJ("./layerObjects/GSHELL.obj");
if(!gshell)
{
printf("unable to load GSHELL.obj\n");
}
banana = glmReadOBJ("./layerObjects/banana.obj");
if(!banana)
{
printf("unable to load banana.obj\n");
}
}
void kartObjLayer::drawShield(double x, double y, int count, item_values_t type)
{
static int shieldRotate = 0;
if(shieldRotate>= 360)
{
shieldRotate = 0;
}
shieldRotate += 5;
glPushMatrix();
glTranslatef(x, y, 0);
glRotatef(shieldRotate, 0,0, 1);
glPushMatrix();
glTranslatef(0, -1.5, 0);
if(count>2)
{
if(type == REDSHELL)
{
drawRShell(0, 0);
}else
{
drawGShell(0, 0);
}
}
glPopMatrix();
glPushMatrix();
glTranslatef(-1.5, 1.5, 0);
if(count > 1)
{
if(type == REDSHELL)
{
drawRShell(0, 0);
}else
{
drawGShell(0, 0);
}
}
glPopMatrix();
glPushMatrix();
glTranslatef(1.5, 1.5, 0);
if(type == REDSHELL)
{
drawRShell(0, 0);
}else
{
drawGShell(0, 0);
}
glPopMatrix();
glPopMatrix();
}
void kartObjLayer::drawGShell(double x, double y)
{
glPushMatrix();
//glutSolidCube(1);
glTranslatef(x-.3, y-.3, -.5);
glRotatef(-110, 0, 0, 1);
glRotatef(90, 1, 0, 0);
glScalef(.5, .5, .5);
glScalef(.5, .5, .5);
glmDraw(gshell, GLM_COLOR);
glPopMatrix();
}
void kartObjLayer::drawRShell(double x, double y)
{
glPushMatrix();
//glutSolidCube(1);
glTranslatef(x-.3, y-.3, -.5);
glRotatef(-110, 0, 0, 1);
glRotatef(90, 1, 0, 0);
glScalef(.5, .5, .5);
glScalef(.5, .5, .5);
glmDraw(rshell, GLM_COLOR);
glPopMatrix();
}
void kartObjLayer::drawBanana(double x, double y)
{
glPushMatrix();
//glutSolidCube(1);
glTranslatef(x, y-.4, -.7);
glRotatef(160, 0, 0, 1);
glRotatef(90, 1, 0, 0);
glScalef(.2, .2, .2);
glmDraw(banana, GLM_COLOR);
}
glPopMatrix();
void kartObjLayer::drawWepBox(double x, double y)
{
glPushMatrix();
//glutSolidCube(1);
glTranslatef(x, y+.3, -.5);
glRotatef(45, 0, 0, 1);
glRotatef(90, 1, 0, 0);
glScalef(.5, .5, .5);
glScalef(.35, .35, .35);
glmDraw(wepBox1, GLM_COLOR);
}
glPopMatrix();
void kartObjLayer::drawSphere(float xSize, float ySize, float zSize)
{
// save the transformation state
glPushMatrix();
// locate it in the scene
glMatrixMode(GL_MODELVIEW);
// note that center of cube is at origin
glTranslatef(0, 0, 0);
glScalef(xSize, ySize, zSize);
// draw the cube - the parameter is the length of the sides
//glutSolidCube(2.0);
glutSolidSphere(1, 15, 15);
// recover the transform state
glPopMatrix();
}
return;
void kartObjLayer::drawTrafficLight()
{
GLfloat defaultColor[4];
GLfloat redLightColor[4];
redLightColor[1] = 0; redLightColor[2] = 0; redLightColor[3] = 1.0;
GLfloat greenLightColor[4];
greenLightColor[0] = 0; greenLightColor[2] = 0;greenLightColor[3] = 1.0;
GLfloat yellowLightColor[4];
yellowLightColor[0] = 0; yellowLightColor[2] = 0;yellowLightColor[3] = 1.0;
GLfloat dontWalkLightColor[4];
dontWalkLightColor[2] = 0;dontWalkLightColor[3] = 1.0;
GLfloat lightDir[] = {0,-1, 1, 0.0};
GLfloat diffuseComp[] = {1.0, 1.0, 1.0, 1.0};
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_NORMALIZE);
glEnable(GL_COLOR_MATERIAL);
glLightfv(GL_LIGHT0, GL_POSITION, lightDir);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseComp);
if (lightState == REDLIGHT) {
redLightColor[0] = 1;
greenLightColor[1] = 0.4;
yellowLightColor[0] = .4;
yellowLightColor[1] = .4;
}
else if (lightState == GREENLIGHT ) {
redLightColor[0] = 0.4;
greenLightColor[1] = 1.0;
yellowLightColor[0] = .4;
yellowLightColor[1] = .4;
}else if (lightState == YELLOWLIGHT)
{
redLightColor[0] = 0.4;
greenLightColor[1] = .4;
yellowLightColor[0] = 1;
yellowLightColor[1] = 1;
}
glPushMatrix();
// draw red light on top - just a red box
glColor3fv(redLightColor);
//glMaterialfv(GL_FRONT, GL_DIFFUSE, redLightColor);
glTranslatef(-2, 10, 0);
drawSphere(1, 0.1, 1);
// draw yellow light below red light
//glMaterialfv(GL_FRONT, GL_DIFFUSE, greenLightColor);
glColor3fv(yellowLightColor);
glTranslatef(2, 0, 0);
drawSphere(1, 0.1, 1);
// draw green light below red light
//glMaterialfv(GL_FRONT, GL_DIFFUSE, greenLightColor);
glColor3fv(greenLightColor);
glTranslatef(2, 0, 0);
drawSphere(1, 0.1, 1);
defaultColor[2] = 1;
defaultColor[3] = 1.0;
defaultColor[0]= 1.0;
defaultColor[1] = 1.0;
glColor3fv(defaultColor);
glPopMatrix();
}
/*
void *moveShell(void *arg)
{
while(shellTrans< 10)
{
shellTrans= shellTrans+.5;
usleep(50000);
}
shellShot = false;
}
void shootShell()
{
shellShot = true;
shellTrans = 0;
pthread_create(&shellThread, NULL, moveShell, (void *) 1);
}
*/
x.
kartViewController.cpp
#include "kartViewController.h"
#define SHELLSPEED 3
#define STEADYSPEED .5
#define NETWORK
#define ARDUINO
#define PI 3.14159
/*public functions*/
void kartViewController::init(int player)
{
/*3d inits*/
myLayer.init();
fps = 0;
/*hud inits*/
myHud.init(1, 3, 1);
raceTimer= 0;
wepBuffer = NO_ITEM;
raceStart = false;
scrollWep = false;
vibrateScreen = false;
runStar = false;
fireWep = false;
/*sound init*/
bgMusic.OpenFromFile("./sounds/rainbowroad.ogg");
itemreel.LoadFromFile("./sounds/itemreel.wav");
gotitem.LoadFromFile("./sounds/gotitem.wav");
starpower.LoadFromFile("./sounds/starpower.wav");
startup.LoadFromFile("./sounds/startup.wav");
countdown.LoadFromFile("./sounds/countdown.wav");
gothit.LoadFromFile("./sounds/gothit.wav");
cputhrow.LoadFromFile("./sounds/cputhrow.wav");
itemdrop.LoadFromFile("./sounds/itemdrop.wav");
woohoo.LoadFromFile("./sounds/woohoo.wav");
boost.LoadFromFile("./sounds/boost.wav");
starSound.SetBuffer(starpower);
lightSound.SetBuffer(startup);
hitSound.SetBuffer(gothit);
throwSound.SetBuffer(cputhrow);
dropSound.SetBuffer(itemdrop);
shieldSound.SetBuffer(woohoo);
mushroomSound.SetBuffer(boost);
starSound.SetLoop(true);
bgMusic.SetLoop(true);
bgMusic.SetVolume(30);
lightSound.Play();
/*networks init*/
#ifdef NETWORK
char *buffer;
buffer = (char *) malloc(sizeof(buffer));
myNetwork.init(1, player);
myNetwork.send_data(buffer);
free(buffer);
#endif
#ifdef ARDUINO
myArduino.init("/dev/tty.usbserial-A700fiFR", 0);
#endif
for(int i = 0; i < MAXOBJECTS; i++)
{
objBuffer[i].type = NO_ITEM;
}
// myLayer.addObj(0, 0, REDSHELL);
// objBuffer[0].type = REDSHELL;
// objBuffer[0].x = 0;
// objBuffer[0].y = 10;
}
void kartViewController::start()
{
}
if(!raceStart && !myLayer.showLight)
{
lightSound.SetBuffer(countdown);
lightSound.Play();
myLayer.lightState = REDLIGHT;
myLayer.showLight = true;
}
void kartViewController::pickUpWep(item_values_t wep)
{
if(wepBuffer == NO_ITEM && wep != NO_ITEM)
{
wepBuffer = wep;
scrollWep = true;
scrollWepSound.SetBuffer(itemreel);
scrollWepSound.Play();
}
}
void kartViewController::useWep()
{
if(scrollWep)
{
scrollWep = false;
scrollWepSound.Stop();
scrollWepSound.SetBuffer(gotitem);
scrollWepSound.Play();
}
#ifndef NETWORK
else if(wepBuffer != NO_ITEM)
{
if(wepBuffer == STAR)
{
runStar = true;
starSound.Play();
}else if(wepBuffer == REDSHELL || wepBuffer == GREENSHELL)
{
throwSound.Play();
}else if(wepBuffer == MUSHROOM)
{
mushroomSound.Play();
}else if(wepBuffer == BANANA)
{
dropSound.Play();
}else if(wepBuffer == MRSHELL || wepBuffer == MGSHELL)
{
shieldSound.Play();
}
wepBuffer = NO_ITEM;
}
#else
else
{
fireWep = true;
}
#endif
}
void kartViewController::gotHit()
{
vibrateScreen = true;
hitSound.Play();
}
void kartViewController::step()
{
//cout << "starting controller step" << endl;
calcFPS(getTickCount());
#ifdef NETWORK
char inPacket[150];
if(myNetwork.recv_data(inPacket) > 20)
{
parsePacket(inPacket);
}
sendPacket();
makeObjectTravel();
#endif
}
detLight();
//cout << "done detlight" << endl;
placeRaceTime(getTickCount());
//cout << "done placeracetie" << endl;
placeWep();
//cout << "done placewep" << endl;
modifyScreen();
//cout << "done modifyscreen" << endl;
myLayer.step();
//cout << "done layerstep" << endl;
myHud.step();
//cout << "done hudstep" << endl;
void kartViewController::testButton(char *buff)
{
}
myArduino.overWrite(buff);
/*private functions*/
unsigned kartViewController::getTickCount()
{
struct timeval tv;
if(gettimeofday(&tv, NULL) != 0)
return 0;
return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
}
void kartViewController::calcFPS(int currTime)
{
static int lastTFrame = currTime;
static int frameCounter = 0;
if(currTime - lastTFrame >= 1000)
{
fps = frameCounter;
frameCounter = 0;
lastTFrame = getTickCount();
//cout << fps << endl;
}else
{
frameCounter++;
}
}
void kartViewController::placeRaceTime(int currTime)
{
static int startTime = -1;
if(raceStart && startTime == -1)
{
startTime = currTime;
}
if(startTime != -1)
{
raceTimer = currTime - startTime;
}else
{
raceTimer= 0;
}
myHud.setRaceTime(raceTimer);
}
void kartViewController::placeWep()
{
static int currSetWep = 0;
static int scrollTime = 0;
if(scrollWep)
{
if(getTickCount()-scrollTime >= 100)
{
scrollTime = getTickCount();
currSetWep++;
}
myHud.changeHud(-1, -1, currSetWep%NUMWEP +1, -1);
if(currSetWep>= NUMWEP*5)
{
scrollWep = false;
scrollWepSound.Stop();
scrollWepSound.SetBuffer(gotitem);
scrollWepSound.Play();
}
}else
{
myHud.changeHud(-1, -1, wepBuffer, -1);
currSetWep = 0;
}
}
void kartViewController::modifyScreen()
{
static int screen = 0;
static int vibTime = 0;
static int color = -1;
static int colorTime = 0;
if(vibrateScreen)
{
if(getTickCount()-vibTime >= 10)
{
screen++;
vibTime = getTickCount();
}
myHud.setScreenOffset( (screen%2*10) -5 , 0, 0);
if(screen >= 6)
{
vibrateScreen = false;
}
}else
{
myHud.setScreenOffset(0, 0, 0);
screen = 0;
}
if(runStar)
{
if(getTickCount()-vibTime >= 10)
{
color++;
}
if(color%3 == 0)
{
myHud.setScreenColor(1, .4, .4);
}else if(color%3 == 1)
{
myHud.setScreenColor(1, 1, 0);
}else
{
myHud.setScreenColor(1, 1, 1);
}
}else
{
color = 0;
myHud.setScreenColor(1, 1, 1);
}
}
void kartViewController::detLight()
{
static int lightTimer = -1;
if(myLayer.showLight)
{
if(lightTimer == -1)
{
lightTimer = getTickCount();
}
if(getTickCount() - lightTimer >= 3000)
{
myLayer.showLight= false;
raceStart = true;
lightTimer = -1;
bgMusic.Play();
}else if((getTickCount() - lightTimer) >= 2000)
{
myLayer.lightState = GREENLIGHT;
}
}else if((getTickCount() - lightTimer) >= 1000)
{
myLayer.lightState = YELLOWLIGHT;
}
}
void kartViewController::makeObjectTravel()
{
static double lastTFrame = -1;
double dTime;
if(lastTFrame == -1)
{
lastTFrame = getTickCount();
}
dTime = getTickCount() - lastTFrame;
for(int i = 0; i < MAXOBJECTS; i++)
{
if(objBuffer[i].type != NO_ITEM
&& myLayer.objects[i].type == objBuffer[i].type)
{
if(objBuffer[i].y > myLayer.objects[i].y &&
objBuffer[i].y - myLayer.objects[i].y > .3)
{
myLayer.objects[i].y = SHELLSPEED * (dTime/1000) + myLayer.objects[i].y;
}else if(objBuffer[i].y < myLayer.objects[i].y &&
myLayer.objects[i].y - objBuffer[i].y > .3)
{
myLayer.objects[i].y = -SHELLSPEED * (dTime/1000) + myLayer.objects[i].y;
}
if(objBuffer[i].x > myLayer.objects[i].x &&
objBuffer[i].x - myLayer.objects[i].x > .3)
{
myLayer.objects[i].x = SHELLSPEED * (dTime/1000) + myLayer.objects[i].y;
}else if(objBuffer[i].x < myLayer.objects[i].y &&
myLayer.objects[i].x - objBuffer[i].y > .3)
{
myLayer.objects[i].x = -SHELLSPEED * (dTime/1000) + myLayer.objects[i].y;
}
}
}
lastTFrame = getTickCount();
}
void kartViewController::parsePacket(char *inpacket)
{
char *tok;
item_values_t currItem = NO_ITEM;
int tokCount = 0;
int speed;
int key = 0;
double x;
double y;
static item_values_t currShield = NO_ITEM;
static int currSpeed = -1;
cout << inpacket << endl;
tok = strtok(inpacket, " ,");
while(tok)
{
//cout << "tokenizing" << endl;
if(tokCount == 1)
{
currItem = (item_values_t) atoi(tok);
if(wepBuffer != currItem)
{
if(currItem == NO_ITEM)
{
if(wepBuffer == REDSHELL || wepBuffer == GREENSHELL)
{
throwSound.Play();
}else if(wepBuffer == MUSHROOM)
{
mushroomSound.Play();
}else if(wepBuffer == BANANA)
{
dropSound.Play();
}else if(wepBuffer == MRSHELL || wepBuffer == MGSHELL)
{
shieldSound.Play();
}
wepBuffer = NO_ITEM;
}
}else
{
pickUpWep(currItem);
}
}else if(tokCount == 2)
{
currItem = (item_values_t)atoi(tok);
tok = strtok(NULL, " ,");
tokCount++;
if(currItem == STAR && currShield != STAR)
{
runStar = true;
starSound.Play();
}else if(currItem == MRSHELL || currItem == REDSHELL)
{
myLayer.setShield(atoi(tok), REDSHELL);
}else if(currItem == MGSHELL || currItem == GREENSHELL)
{
myLayer.setShield(atoi(tok), GREENSHELL);
}else
{
if(currShield == STAR && currItem != STAR)
{
cout << "stopping star" << endl;
runStar = false;
starSound.Stop();
}else
{
myLayer.setShield(0, NO_ITEM);
}
}
currShield = currItem;
}else if(tokCount == 4)
{
//lap No.
if(!raceStart && atoi(tok) != 0)
{
start();
}
myHud.changeHud(-1, atoi(tok), -1, -1);
}else if(tokCount == 5)
{
//place
myHud.changeHud(atoi(tok), -1, -1, -1);
}else if (tokCount == 6)
{
#ifdef ARDUINO
myArduino.setSpeed(atoi(tok));
#endif
if(currSpeed != 0 && atoi(tok) == 0)
{
gotHit();
}
currSpeed = atoi(tok);
}
else if(tokCount > 7)
{
//type
currItem = (item_values_t) atoi(tok);
tok = strtok(NULL, " ,");
tokCount++;
//x position
x = (double)atoi(tok);
tok = strtok(NULL, " ,");
tokCount++;
//yposition
y = (double)atoi(tok);
if(currItem == NO_ITEM)
{
myLayer.removeObj(key);
objBuffer[key].type = NO_ITEM;
objBuffer[key].x = 0;
objBuffer[key].y = 0;
}else if(currItem != myLayer.objects[key].type)
{
myLayer.updateObj(key, x/(10.0), y/(10.0), currItem);
objBuffer[key].type = currItem;
objBuffer[key].x = x/(10.0);
objBuffer[key].y = y/(10.0);
}else
{
myLayer.objects[key].type = currItem;
objBuffer[key].type = currItem;
objBuffer[key].x = x/(10.0);
objBuffer[key].y = y/(10.0);
}
key++;
}
}
}
tok = strtok(NULL, " ,");
tokCount++;
void kartViewController::sendPacket()
{
static int lastTFrame = 0;
static char outPacket[40];
#ifdef ARDUINO
if(strlen(outPacket) < 3 && getTickCount()- lastTFrame >= 250)
{
myArduino.readData(outPacket);
cout << "packet length " << strlen(outPacket) << endl;
}
#else
strcpy(outPacket, "0,0,0,0");
#endif
if(getTickCount() -lastTFrame >= 250 && strlen(outPacket) > 3)
{
if(fireWep)
{
strcat(outPacket, ",1");
fireWep = false;
}else
{
strcat(outPacket, ",0");
}
}
}
cout << "sending " << outPacket <<endl;
myNetwork.send_data(outPacket);
lastTFrame = getTickCount();
memset(outPacket, 0, 40);
xi.
main.cpp (Laptop)
// mastergraphics.cpp : main project file.
#include "kartViewController.h"
#include "main.h"
//hud* myHud = new hud();
//kartObjLayer *myLayer = new kartObjLayer();
int testx = 0;
int testy = 0;
int testz = 0;
int testWep = 0;
bool fullScreened;
void setUpView()
{
glLoadIdentity();
gluLookAt(0, -10, 0, 0.0, 0.0, 0.0, 0.0, 0, 1.0);
glTranslatef(0.0f,-10, 0.0f);
return;
}
void reshapeCallback(int w, int h)
{
printf("width %d, height %d\n", w, h);
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60, 1.0, 0.1, 200.0);
}
glMatrixMode(GL_MODELVIEW);
void clear()
{
}
void glutKeyboard(unsigned char key, int x, int y)
{
switch (key)
{
case 27:
{
clear();
exit(0);
}
case '1':
{
myController.start();
break;
}
case '2':
{
myController.pickUpWep((item_values_t)testWep);
break;
}
case 'W':
{
myController.useWep();
break;
}
case 'e':
{
myController.gotHit();
break;
}
case 'r':
{
break;
}
case 'b':
{
myController.useWep();
break;
}
case 'p':
{
testx++;
break;
}
case 'P':
{
testx--;
break;
}
case 'o':
{
testy++;
break;
}
case 'O':
{
testy--;
break;
}
case 'i':
{
testz++;
break;
}
case 'I':
{
testz--;
break;
}
case 'a':
{
myController.testButton("a");
break;
}
case 's':
{
myController.testButton("s");
break;
}
case 'z':
{
myController.testButton("z");
break;
}
case 'x':
{
myController.testButton("x");
break;
}
case 'q':
{
myController.testButton("q");
break;
}
case 'w':
{
myController.testButton("w");
break;
}
}
}
void glutSpecialKeyboard(int value, int x, int y)
{
switch (value)
{
case GLUT_KEY_F1:
{
testWep++;
if(testWep > NUMWEP)
{
testWep = 0;
}
}
case GLUT_KEY_F2:
{
}
}
}
if ( !fullScreened )
{
glutFullScreen();
}else
{
glutPositionWindow( 0, 20 );
glutReshapeWindow( 640, 480 );
}
fullScreened = !fullScreened;
break;
void glutDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
setUpView();
glTranslatef(testx, testy, testz);
myController.step();
//myLayer->step();
glutSwapBuffers();
}
void glutMouseFunc( int button, int state, int x, int y )
{
#ifdef DEBUG
if ( state == GLUT_DOWN && button == GLUT_LEFT_BUTTON )
{
printf("screen coords %d, %d\n", x, y);
}
#endif
}
void InitializeOpenGL(int argc, char **argv)
{
string windowName = "Player ";
glutInitDisplayMode( GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA | GLUT_MULTISAMPLE );
glutInitWindowPosition( 0, 0 );
glutInitWindowSize( 640, 480 );
glutInit( &argc, argv );
windowName.append(argv[1]);
glutCreateWindow(windowName.c_str());
glutReshapeFunc(reshapeCallback);
glutDisplayFunc(glutDisplay);
glutKeyboardFunc(glutKeyboard);
glutSpecialFunc(glutSpecialKeyboard);
glutIdleFunc(glutDisplay);
glutMouseFunc(glutMouseFunc);
glutFullScreen();
fullScreened = true;
glutMainLoop();
}
int main(int argc, char** argv)
{
int self;
if(argc != 2)
{
fprintf(stderr, "Usage: %s kartnumber\n", argv[0]);
return -1;
}
self = atoi(argv[1]);
if(self == 0)
{
fprintf(stderr, "Invalid number given: %d\n", self);
return -1;
}
//
myController.init(self);
InitializeOpenGL(argc, argv);
clear();
}
return 0;