Samples
Samples
Snake
Language: Silver, Platform: Cooper, Category: Android
https://github.com/remobjects/ElementsSamples/tree/master/Silver/Cooper/Android/Snake
-
org.me.snake
-
References
- android
- cooper
- swift
- Source Files
-
Other Files
- Properties\AndroidManifest.android-xml
- res\layout\snake_layout.xml
- res\values\attrs.xml
- res\values\colors.xml
- res\values\strings.xml
- res\drawable-xhdpi\ic_launcher.png
- res\drawable-mdpi\dpad_down.xml
- res\drawable-mdpi\dpad_left.xml
- res\drawable-mdpi\dpad_right.xml
- res\drawable-mdpi\dpad_up.png
- res\drawable-mdpi\greenstar.png
- res\drawable-mdpi\ic_launcher.png
- res\drawable-mdpi\redstar.png
- res\drawable-mdpi\yellowstar.png
- res\drawable-ldpi\ic_launcher.png
- res\drawable-hdpi\ic_launcher.png
-
References
SnakeView.swift
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License")
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Translated to Swift by marc hoffman, RemObjects Software.
*
*/
import android.content
import android.content.res
import android.os
import android.util
import android.view
import android.widget
import java.util
enum Direction : Int32 {
case NORTH = 1
case SOUTH = 2
case EAST = 3
case WEST = 4
}
enum Mode : Int32 {
case PAUSE = 0
case READY = 1
case RUNNING = 2
case LOSE = 3
}
enum Move : Int32 {
case MOVE_LEFT = 0
case MOVE_UP = 1
case MOVE_DOWN = 2
case MOVE_RIGHT = 3
}
enum Star : Int32 {
case NONE = 0
case RED_STAR = 1
case YELLOW_STAR = 2
case GREEN_STAR = 3
}
/**
* SnakeView: implementation of a simple game of Snake
*/
public class SnakeView : TileView {
class let TAG = "SnakeView"
/**
* Current mode of application: READY to run, RUNNING, or you have already lost. static final
* ints are used instead of an enum for performance reasons.
*/
var _mode = Mode.READY
/**
* Current direction the snake is headed.
*/
var _direction = Direction.NORTH
var _nextDirection = Direction.NORTH
/**
* _score: Used to track the number of apples captured _moveDelay: number of milliseconds
* between snake movements. This will decrease as apples are captured.
*/
var _score = 0
var _moveDelay = 600
/**
* _lastMove: Tracks the absolute time when the snake last moved, and is used to determine if a
* move should be made based on _moveDelay.
*/
var _lastMove: Int?
/**
* _statusText: Text shows to the user in some run states
*/
var _statusText: TextView!
/**
* _arrowsView: View which shows 4 arrows to signify 4 directions in which the snake can move
*/
var _arrowsView: View!
/**
* _backgroundView: Background View which shows 4 different colored triangles pressing which
* moves the snake
*/
var _backgroundView: View!
/**
* _snakeTrail: A list of Coordinates that make up the snake's body _appleList: The secret
* location of the juicy apples the snake craves.
*/
var _snakeTrail = ArrayList<Coordinate>()
var _appleList = ArrayList<Coordinate>()
/**
* Everyone needs a little randomness in their life
*/
let RNG = Random()
/**
* Create a simple handler that we can use to cause animation to happen. We set ourselves as a
* target and we can use the sleep() function to cause an update/invalidate to occur at a later
* date.
*/
let _redrawHandler = RefreshHandler(outer: self)
class RefreshHandler : Handler {
let outer: SnakeView!; // todo: should not need to be nullable if set in init()
public init(outer: SnakeView) {
self.outer = outer;
}
public override func handleMessage(_ msg: Message!) {
outer.update()
outer.invalidate()
}
func sleep(milliseconds delayMillis: Int) {
removeMessages(0)
sendMessageDelayed(obtainMessage(0), delayMillis)
}
}
/**
* Constructs a SnakeView based on inflation from XML
*
* @param context
* @param attrs
*/
public init(context: Context, attrs: AttributeSet) {
super.init(context, attrs)
initSnakeView(context)
}
public init(context: Context, attrs: AttributeSet, defStyle: Int) {
super.init(context, attrs, defStyle)
initSnakeView(context)
}
func initSnakeView(_ context: Context) {
setFocusable(true)
let r = self.getContext().getResources()
resetTiles(4)
loadTile(tile: r.getDrawable(R.drawable.redstar)!, key: .RED_STAR)
loadTile(tile: r.getDrawable(R.drawable.yellowstar)!, key: .YELLOW_STAR)
loadTile(tile: r.getDrawable(R.drawable.greenstar)!, key: .GREEN_STAR)
}
func initNewGame() {
_snakeTrail.clear()
_appleList.clear()
// For now we're just going to load up a short default eastbound snake
// that's just turned north
_snakeTrail.add(Coordinate(X: 7, Y: 7))
_snakeTrail.add(Coordinate(X: 6, Y: 7))
_snakeTrail.add(Coordinate(X: 5, Y: 7))
_snakeTrail.add(Coordinate(X: 4, Y: 7))
_snakeTrail.add(Coordinate(X: 3, Y: 7))
_snakeTrail.add(Coordinate(X: 2, Y: 7))
_nextDirection = .NORTH
// Two apples to start with
addRandomApple()
addRandomApple()
_moveDelay = 600
_score = 0
}
/**
* Given a ArrayList of coordinates, we need to flatten them into an array of ints before we can
* stuff them into a map for flattening and storage.
*
* @param cvec : a ArrayList of Coordinate objects
* @return : a simple array containing the x/y values of the coordinates as
* [x1,y1,x2,y2,x3,y3...]
*/
func coordArrayListToArray(_ cvec: ArrayList<Coordinate>) -> Int32[]! {
var rawArray = Int32[]!(cvec.size() * 2)
var i = 0
for c in cvec {
rawArray[i++] = c.X
rawArray[i++] = c.Y
}
return rawArray
}
/**
* Save game state so that the user does not lose anything if the game process is killed while
* we are in the background.
*
* @return a Bundle with this view's state
*/
func saveState() -> Bundle {
let map = Bundle()
map.putIntArray("_appleList", coordArrayListToArray(_appleList))
map.putInt("_direction", _direction as! Int)
map.putInt("_nextDirection", _nextDirection as! Int)
map.putDouble("_moveDelay", _moveDelay)
map.putLong("_score", _score)
map.putIntArray("_snakeTrail", coordArrayListToArray(_snakeTrail))
return map
}
/**
* Given a flattened array of ordinate pairs, we reconstitute them into a ArrayList of
* Coordinate objects
*
* @param rawArray : [x1,y1,x2,y2,...]
* @return a ArrayList of Coordinates
*/
func coordArrayToArrayList(_ rawArray: Int32[]!) -> ArrayList<Coordinate> {
let coordArrayList = ArrayList<Coordinate>()
let coordCount = rawArray.length
for var index = 0; index < coordCount; index += 2 {
let c = Coordinate(X: rawArray[index], Y: rawArray[index + 1])
coordArrayList.add(c)
}
return coordArrayList
}
/**
* Restore game state if our process is being relaunched
*
* @param icicle a Bundle containing the game state
*/
func restoreState(_ icicle: Bundle) {
setMode(.PAUSE)
_appleList = coordArrayToArrayList(icicle.getIntArray("_appleList"))
_direction = icicle.getInt("_direction") as! Direction
_nextDirection = icicle.getInt("_nextDirection") as! Direction
_moveDelay = icicle.getInt("_moveDelay")
_score = icicle.getLong("_score")
_snakeTrail = coordArrayToArrayList(icicle.getIntArray("_snakeTrail"))
}
/**
* Handles snake movement triggers from Snake Activity and moves the snake accordingly. Ignore
* events that would cause the snake to immediately turn back on itself.
*
* @param direction The desired direction of movement
*/
func moveSnake(_ direction: Move) {
if (direction == Move.MOVE_UP) {
if (_mode == Mode.READY || _mode == Mode.LOSE) {
/*
* At the beginning of the game, or the end of a previous one,
* we should start a new game if UP key is clicked.
*/
initNewGame()
setMode(.RUNNING)
update()
return
}
if (_mode == Mode.PAUSE) {
/*
* If the game is merely paused, we should just continue where we left off.
*/
setMode(.RUNNING)
update()
return
}
if (_direction != Direction.SOUTH) {
_nextDirection = Direction.NORTH
}
return
}
if (direction == Move.MOVE_DOWN) {
if (_direction != Direction.NORTH) {
_nextDirection = .SOUTH
}
return
}
if (direction == Move.MOVE_LEFT) {
if (_direction != Direction.EAST) {
_nextDirection = .WEST
}
return
}
if (direction == Move.MOVE_RIGHT) {
if (_direction != Direction.WEST) {
_nextDirection = .EAST
}
return
}
}
/**
* Sets the Dependent views that will be used to give information (such as "Game Over" to the
* user and also to handle touch events for making movements
*
* @param newView
*/
func setDependentViews(msgView: TextView, arrowView: View, backgroundView: View) {
_statusText = msgView
_arrowsView = arrowView
_backgroundView = backgroundView
}
/**
* Updates the current mode of the application (RUNNING or PAUSED or the like) as well as sets
* the visibility of textview for notification
*
* @param newMode
*/
func setMode(_ newMode: Mode) {
let oldMode = _mode
_mode = newMode
if (newMode == Mode.RUNNING && oldMode != Mode.RUNNING) {
// hide the game instructions
_statusText.setVisibility(View.INVISIBLE)
update()
// make the background and arrows visible as soon the snake starts moving
_arrowsView.setVisibility(View.VISIBLE)
_backgroundView.setVisibility(View.VISIBLE)
return
}
let res = getContext().getResources()
var str = ""
if newMode == Mode.PAUSE {
_arrowsView.setVisibility(View.GONE)
_backgroundView.setVisibility(View.GONE)
str = res.getText(R.string.mode_pause).toString()!
}
if newMode == Mode.READY {
_arrowsView.setVisibility(View.GONE)
_backgroundView.setVisibility(View.GONE)
str = res.getText(R.string.mode_ready).toString()!
}
if newMode == Mode.LOSE {
_arrowsView.setVisibility(View.GONE)
_backgroundView.setVisibility(View.GONE)
str = res.getString(R.string.mode_lose, _score)!
}
_statusText.setText(str)
_statusText.setVisibility(View.VISIBLE)
}
/**
* @return the Game state as Running, Ready, Paused, Lose
*/
func getGameState() -> Mode {
return _mode
}
/**
* Selects a random location within the garden that is not currently covered by the snake.
* Currently _could_ go into an infinite loop if the snake currently fills the garden, but we'll
* leave discovery of this prize to a truly excellent snake-player.
*/
func addRandomApple() {
var newCoord: Coordinate? = nil
var found = false
while (!found) {
// Choose a new location for our apple
let newX = 1 + RNG.nextInt(XTileCount - 2)
let newY = 1 + RNG.nextInt(YTileCount - 2)
newCoord = Coordinate(X: newX, Y: newY)
// Make sure it's not already under the snake
var collision = false
let snakelength = _snakeTrail.size()
for index in 0 ..< snakelength {
if (_snakeTrail.get(index).equals(newCoord)) {
collision = true
}
}
// if we're here and there's been no collision, then we have
// a good location for an apple. Otherwise, we'll circle back
// and try again
found = !collision
}
if (newCoord == nil) {
Log.e(TAG, "Somehow ended up with a null newCoord!")
}
_appleList.add(newCoord!)
}
/**
* Handles the basic update loop, checking to see if we are in the running state, determining if
* a move should be made, updating the snake's location.
*/
func update() {
if (_mode == Mode.RUNNING) {
let now = System.currentTimeMillis()!
if (_lastMove == nil || (now - _lastMove! > _moveDelay)) {
clearTiles()
updateWalls()
updateSnake()
updateApples()
_lastMove = now
}
_redrawHandler.sleep(milliseconds: _moveDelay)
}
}
/**
* Draws some walls.
*/
func updateWalls() {
for x in 0 ..< XTileCount {
setTile(.GREEN_STAR, x: x, y: 0)
setTile(.GREEN_STAR, x: x, y: YTileCount - 1)
}
for y in 1 ..< YTileCount-1 {
setTile(.GREEN_STAR, x: 0, y: y)
setTile(.GREEN_STAR, x: XTileCount - 1, y: y)
}
}
/**
* Draws some apples.
*/
func updateApples() {
for c in _appleList {
setTile(.YELLOW_STAR, x: c.X, y: c.Y)
}
}
/**
* Figure out which way the snake is going, see if he's run into anything (the walls, himself,
* or an apple). If he's not going to die, we then add to the front and subtract from the rear
* in order to simulate motion. If we want to grow him, we don't subtract from the rear.
*/
func updateSnake() {
var growSnake = false
// Grab the snake by the head
let head = _snakeTrail.get(0)
var newHead = Coordinate(X: 1, Y: 1)
_direction = _nextDirection
switch (_direction) {
case Direction.EAST: // TODO
newHead = Coordinate(X: head.X + 1, Y: head.Y)
break
case Direction.WEST:
newHead = Coordinate(X: head.X - 1, Y: head.Y)
break
case Direction.NORTH:
newHead = Coordinate(X: head.X, Y: head.Y - 1)
break
case Direction.SOUTH:
newHead = Coordinate(X: head.X, Y: head.Y + 1)
break
}
// Collision detection
// For now we have a 1-square wall around the entire arena
if newHead.X < 1 || newHead.Y < 1 || newHead.X > XTileCount - 2 || newHead.Y > YTileCount - 2 {
setMode(.LOSE)
return
}
// Look for collisions with itself
let snakelength = _snakeTrail.size()
for snakeindex in 0 ..< snakelength {
let c = _snakeTrail.get(snakeindex)
if c == newHead {
setMode(.LOSE)
return
}
}
// Look for apples
let applecount = _appleList.size()
for appleindex in 0 ..< applecount {
let c = _appleList.get(appleindex)
if c == newHead {
_appleList.remove(c)
addRandomApple()
_score += 1
_moveDelay = Int(Double(_moveDelay) * 0.9)
growSnake = true
}
}
// push a new head onto the ArrayList and pull off the tail
_snakeTrail.add(0, newHead)
// except if we want the snake to grow
if !growSnake {
_snakeTrail.remove(_snakeTrail.size() - 1)
}
var index = 0
for c in _snakeTrail {
if (index == 0) {
setTile(.YELLOW_STAR, x: c.X, y: c.Y)
} else {
setTile(.RED_STAR, x: c.X, y: c.Y)
}
index += 1
}
}
}
/**
* Simple class containing two integer values and a comparison function. There's probably
* something I should use instead, but this was quick and easy to build.
*/
class Coordinate {
var X: Int
var Y: Int
public init(X: Int, Y: Int) { // TODO: this should be auto-generated
self.X = X;
self.Y = Y;
}
func ==(lhs: Coordinate!, rhs: Coordinate!) -> Bool{
if ((lhs as! Object) == nil) != ((rhs as! Object) == nil) {
return false;
}
if ((lhs as! Object) == nil) {
return true;
}
if (lhs.X == rhs.X) && (lhs.Y == rhs.Y) {
return true
}
return false
}
public override func toString() -> String {
return ("Coordinate: [" + X + "," + Y + "]")! // TODO workaround forconcat returning !
}
}
