遊戲玩法:一開始玩家皆是人類,待遊戲等待時間結束後,會隨機跳選一名玩家為殭屍,只要被殭屍抓到的人類,也會被感染成為殭屍,並且一起感染所剩的人類倖存者,殭屍於100秒限時內感染全部人類,即可獲勝,反之,於時間結束仍有人類倖存者,則為人類獲勝。
此程式為前面範例所使用的技術結合製作的多人GPS遊戲,下圖為遊戲開始畫面、遊戲進行畫面、開發流程圖、程式流程圖、伺服器程式流程圖。
圖一-遊戲開始畫面
圖二-遊戲進行畫面
圖三-開發流程
圖四-程式流程圖
圖五-伺服器程式流程圖
以下為殭屍大戰程式碼:
專案下載:ZombieWars(內含殭屍大戰、客戶測試、伺服器).rar
主程式:ZombieWars.java
package ccc.ZombieWars; import java.security.KeyStore.LoadStoreParameter; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapActivity; import com.google.android.maps.MapController; import com.google.android.maps.MapView; import com.google.android.maps.Overlay; import com.google.android.maps.Projection; import com.google.android.maps.MapView.LayoutParams; import ccc.ZombieWars.R; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; import android.util.AttributeSet; import android.util.Log; import android.view.Display; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.KeyEvent; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import android.widget.Toast; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; public class ZombieWars extends MapActivity { Thread mainThread; Boolean isMainThreadStop = true; int updateFPS = 30; KeyHandler keyHandler = new KeyHandler(); PowerManager.WakeLock wakeLock; MapView mapView; MapController mapController; Rect screenRect; PlayerObj player; GpsSensorObj gpsSendor; Handler handler = new Handler(); List<Runnable> updateSet = new ArrayList<Runnable>(); SingleGameCore singleGameCore = null; MultiplayerGameCore multiplayerGameCore = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 隱藏狀態列 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // 隱藏視窗標題 requestWindowFeature(Window.FEATURE_NO_TITLE); // 防止手機因手持方向不同 而觸發螢幕方向旋轉 setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT); // 電源管理服務取得 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "Zombie PowerControl"); WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); screenRect = new Rect(0, 0, display.getWidth(), display.getHeight()); String ApiKey = "03rGqE_CW0JtuagXBUsB8Zxijve-Y131pYz1jqA"; mapView = new MapView(this, ApiKey); // 設定衛星圖 mapView.setSatellite(true); mapController = mapView.getController(); GeoPoint point = new GeoPoint(24448541, 118322206); mapController.setCenter(point); mapController.setZoom(20); mapView.setDrawingCacheEnabled(true); setContentView(mapView); player = new PlayerObj(this, mapView); gpsSendor = new GpsSensorObj(this, mapView); mainThreadStart(); } /** * 電源控制 防止進入休眠狀態切換 */ protected void powerControl(boolean needWake) { if (needWake && !wakeLock.isHeld()) { wakeLock.acquire(); } else if (!needWake && wakeLock.isHeld()) { wakeLock.release(); } } @Override public boolean onTouchEvent(android.view.MotionEvent event) { // player.setGeoPoint(mapView.getProjection().fromPixels((int)event.getX(), // (int)event.getY())); gpsSendor.geoPoint = mapView.getProjection().fromPixels( (int) event.getX(), (int) event.getY()); return true; }; @Override protected void onPause() { super.onPause(); }; protected static final int MENU_ZoomIn = Menu.FIRST; protected static final int MENU_ZoomOut = Menu.FIRST + 1; protected static final int MENU_Exit = Menu.FIRST + 2; protected static final int MENU_SingleGameStart = Menu.FIRST + 3; protected static final int MENU_MultiplayerGameStart = Menu.FIRST + 4; protected static final int MENU_MultiplayerSendGameStart = Menu.FIRST + 5; protected static final int MENU_CancelGame = Menu.FIRST + 6; @Override public boolean onMenuOpened(int featureId, Menu menu) { menu.removeItem(MENU_MultiplayerSendGameStart); if (singleGameCore == null && multiplayerGameCore == null) { menu.removeItem(MENU_CancelGame); if (menu.findItem(MENU_SingleGameStart) == null) menu.add(1, MENU_SingleGameStart, 0, "單人遊戲"); if (menu.findItem(MENU_MultiplayerGameStart) == null){ menu.add(1, MENU_MultiplayerGameStart, 0, "多人遊戲"); } } else { menu.removeItem(MENU_SingleGameStart); menu.removeItem(MENU_MultiplayerGameStart); if (menu.findItem(MENU_CancelGame) == null) menu.add(1, MENU_CancelGame, 0, "取消遊戲"); if(multiplayerGameCore!=null) if(multiplayerGameCore.canSendStartGame()&&menu.findItem(MENU_MultiplayerSendGameStart) == null) menu.add(1,MENU_MultiplayerSendGameStart, 0, "遊戲開始"); } return super.onMenuOpened(featureId, menu); }; @Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(0, MENU_ZoomIn, 0, "放大"); menu.add(0, MENU_ZoomOut, 0, "縮小"); menu.add(1, MENU_Exit, 0, "離開"); return super.onCreateOptionsMenu(menu); }; @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_ZoomIn: mapController.zoomIn(); break; case MENU_ZoomOut: mapController.zoomOut(); break; case MENU_Exit: exit(); break; case MENU_SingleGameStart: if (singleGameCore == null) { singleGameCore = new SingleGameCore(this); } break; case MENU_MultiplayerGameStart: if (multiplayerGameCore == null) { multiplayerGameCore = new MultiplayerGameCore(this); } break; case MENU_MultiplayerSendGameStart: if (multiplayerGameCore != null) { multiplayerGameCore.sendStartGame(); } break; case MENU_CancelGame: if (singleGameCore != null) { singleGameCore.remove(); } if (multiplayerGameCore != null) { multiplayerGameCore.remove(); } break; } return super.onOptionsItemSelected(item); } void exit() { if (singleGameCore != null) { singleGameCore.remove(); } if (multiplayerGameCore != null) { multiplayerGameCore.remove(); } mainThreadStop(); if (mainThread != null) { try { mainThread.join(); } catch (InterruptedException e) { } } finish();// 結束遊戲 } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { keyHandler.keyDown(keyCode); return super.onKeyDown(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { keyHandler.keyUp(keyCode); return super.onKeyUp(keyCode, event); } public void mainThreadStart() { isMainThreadStop = false; powerControl(true); if (mainThread == null) { mainThread = new Thread(threadRun); mainThread.start(); } else if (!mainThread.isAlive()) { mainThread = new Thread(threadRun); mainThread.start(); } } public void mainThreadStop() { isMainThreadStop = true; powerControl(false); } Runnable threadRun = new Runnable() { public void run() { long delayTime = 1000 / updateFPS; while (!isMainThreadStop) { long startTime = System.currentTimeMillis(); handler.post(mainUpdate); long endTime = System.currentTimeMillis(); long waitTime = delayTime - (startTime - endTime); if (waitTime > 0) { try { Thread.sleep(waitTime); } catch (InterruptedException e) { } } } } }; boolean isKeyDown(int keyCode) { return keyHandler.isKeyDown(keyCode); } Runnable mainUpdate = new Runnable() { public void run() { if (isKeyDown(KeyEvent.KEYCODE_DPAD_RIGHT)) { mapController.scrollBy(10, 0); } if (isKeyDown(KeyEvent.KEYCODE_DPAD_LEFT)) { mapController.scrollBy(-10, 0); } if (isKeyDown(KeyEvent.KEYCODE_DPAD_UP)) { mapController.scrollBy(0, -10); } if (isKeyDown(KeyEvent.KEYCODE_DPAD_DOWN)) { mapController.scrollBy(0, 10); } updateSetRun(); Point gpsPoint = new Point(); Point playPoint = new Point(); Projection projection = mapView.getProjection(); projection.toPixels(gpsSendor.geoPoint, gpsPoint); projection.toPixels(player.drawGeoPoint, playPoint); int dx = gpsPoint.x - playPoint.x; int dy = gpsPoint.y - playPoint.y; if (dx * dx + dy * dy > player.getWidth() * player.getHeight() * 0.2) { player.animateTo(gpsSendor.geoPoint); } dx = screenRect.centerX() - gpsPoint.x; dy = screenRect.centerY() - gpsPoint.y; if (dx * dx + dy * dy > 60 * 60) { gpsSendor.autoUpdateMap(true); } else { gpsSendor.autoUpdateMap(false); } mapView.invalidate(); } }; void updateSetRun() { if (updateSet.size() > 0) { Runnable[] updateArray = new Runnable[updateSet.size()]; updateSet.toArray(updateArray); for (Runnable updateItem : updateArray) { updateItem.run(); } } } @Override protected boolean isRouteDisplayed() { return false; } }
程式:GpsSensorObj.java
package ccc.ZombieWars; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.util.Log; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapView; import com.google.android.maps.Overlay; public class GpsSensorObj extends Overlay { GeoPoint geoPoint; LocationManager locationManager; MapView mapView; public boolean enableAutoUpdateMap = false; public GpsSensorObj(Context context, MapView mapView) { locationManager = (LocationManager) context .getSystemService(Context.LOCATION_SERVICE); this.mapView = mapView; geoPoint = new GeoPoint(mapView.getMapCenter().getLatitudeE6(), mapView .getMapCenter().getLongitudeE6()); mapView.getOverlays().add(this); locationManager.requestLocationUpdates(locationManager.GPS_PROVIDER, 0, 0, locationListener); } LocationListener locationListener = new LocationListener() { public void onStatusChanged(String provider, int status, Bundle extras) { } public void onProviderEnabled(String provider) { } public void onProviderDisabled(String provider) { } public void onLocationChanged(Location location) { updateGeoPoint(location); } }; void autoUpdateMap(boolean enable) { enableAutoUpdateMap = enable; } void updateGeoPoint(Location location) { if (location != null) { geoPoint = new GeoPoint((int) (location.getLatitude() * 1e6), (int) (location.getLongitude() * 1e6)); if (enableAutoUpdateMap) { mapView.getController().animateTo(geoPoint); } } } void updateGeoPoint() { updateGeoPoint(getLocation()); } Location getLocation() { return locationManager .getLastKnownLocation(LocationManager.GPS_PROVIDER); } @Override public void draw(Canvas canvas, MapView mapView, boolean shadow) { Point point = new Point(); mapView.getProjection().toPixels(geoPoint, point); Paint paint = new Paint(); paint.setARGB(150, 255, 255, 255); paint.setStrokeWidth(3); canvas.drawLine(point.x - 10, point.y, point.x + 10, point.y, paint); canvas.drawLine(point.x, point.y - 10, point.x, point.y + 10, paint); canvas.drawText("GpsSensor", 20, 30, paint); canvas.drawText("緯度:" + geoPoint.getLatitudeE6() / 1e6, 20, 40, paint); canvas.drawText("經度:" + geoPoint.getLongitudeE6() / 1e6, 20, 50, paint); super.draw(canvas, mapView, shadow); } }
程式:KeyHandler.java
package ccc.ZombieWars; import java.util.HashMap; import java.util.Map; public class KeyHandler { public Map<Integer, Boolean> MapKeyDown = new HashMap<Integer, Boolean>(); public KeyHandler() { } public void keyDown(int keyCode) { MapKeyDown.put(keyCode, true); } public void keyUp(int keyCode) { MapKeyDown.put(keyCode, false); } public boolean isKeyDown(int keyCode) { Boolean isKeyUp = MapKeyDown.get(keyCode); if (isKeyUp != null) return isKeyUp; else return false; } public boolean isKeyUp(int keyCode) { Boolean isKeyUp = MapKeyDown.get(keyCode); if (isKeyUp != null) return isKeyUp; else return true; } }
程式:MultiplayerGameCore.java
package ccc.ZombieWars; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import ccc.ZombieWars.GameStat.StatType; import ccc.ZombieWars.net.PacketListener; import ccc.ZombieWars.net.PacketModel; import ccc.ZombieWars.net.PacketModel.PacketType; import ccc.ZombieWars.net.client.ClientSocketHandler; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapView; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Paint.Align; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.View; public class MultiplayerGameCore extends View implements Runnable { int clientId = 0; GameStat gameStat; ZombieWars main; PlayerObj player; Map<Integer, PlayerObj> players = new HashMap<Integer, PlayerObj>(); Map<Integer, PlayerObj> human = new HashMap<Integer, PlayerObj>(); Map<Integer, PlayerObj> zombie = new HashMap<Integer, PlayerObj>(); Drawable readyImg; Drawable humanWinImg; Drawable zombieWinImg; ClientSocketHandler socket = new ClientSocketHandler(); long sendPacketTime = 0; boolean isGameStart = false; int sendLat = 0; int sendLng = 0; //String serverHost = "10.0.2.2"; //String serverHost="192.168.60.8"; String serverHost = "192.168.0.1"; int serverPort = 443; public MultiplayerGameCore(ZombieWars context) { super(context); this.main = context; this.player = main.player; readyImg = main.getResources().getDrawable(R.drawable.ready); humanWinImg = main.getResources().getDrawable(R.drawable.humanwin); zombieWinImg = main.getResources().getDrawable(R.drawable.zombiewin); MapView.LayoutParams layout = new MapView.LayoutParams( MapView.LayoutParams.FILL_PARENT, MapView.LayoutParams.FILL_PARENT, 0, 0, MapView.LayoutParams.TOP_LEFT); gameStat = new GameStat(System.currentTimeMillis() + 3000); main.mapView.addView(this, layout); main.updateSet.add(this); socket.setPacketListener(packetListener); Thread connectThread = new Thread(connectServer); connectThread.start(); } Runnable connectServer = new Runnable() { public void run() { gameStat.resetTime(System.currentTimeMillis() + 2000); socket.Connect(serverHost, serverPort); if (socket.isConnectError) { gameStat.resetTime(System.currentTimeMillis() + 3000); } } }; PacketListener packetListener = new PacketListener() { public void receivePacket(PacketModel packetModel) { ReceivePacket receivePacket = new ReceivePacket(packetModel); main.handler.post(receivePacket); } public void closeCallback() { main.handler.post(closeCallback); } }; class ReceivePacket implements Runnable { PacketModel packetModel; int id, lat, lng; public ReceivePacket(PacketModel packetModel) { this.packetModel = packetModel; } public void run() { switch (packetModel.getType()) { case getClientId: clientId = Integer.parseInt(packetModel.get("id")); addPlayer(clientId, player); break; case startGame: isGameStart = true; break; case countdownTimeMillis: if (gameStat.nowStatType == StatType.game) { int countdownTimeMillis = Integer.parseInt(packetModel .get("value")); gameStat.resetTime(System.currentTimeMillis() + countdownTimeMillis); } break; case overGame: gameStat.resetTime(System.currentTimeMillis() + 3000); String win = packetModel.get("win"); if (win.equals("human")) gameStat.nowStatType = StatType.humanWin; else gameStat.nowStatType = StatType.zombieWin; printfPacket(packetModel); break; case updateGps: id = Integer.parseInt(packetModel.get("id")); lat = Integer.parseInt(packetModel.get("lat")); lng = Integer.parseInt(packetModel.get("lng")); updateGps(id, lat, lng); break; case addPlayer: id = Integer.parseInt(packetModel.get("id")); lat = Integer.parseInt(packetModel.get("lat")); lng = Integer.parseInt(packetModel.get("lng")); addPlayer(id, lat, lng); break; case removePlayer: id = Integer.parseInt(packetModel.get("id")); removePlayer(id); break; case killHuman: id = Integer.parseInt(packetModel.get("id")); killHuman(id); break; default: printfPacket(packetModel); break; } } } Runnable closeCallback = new Runnable() { public void run() { socket.isConnectError = true; } }; void printfPacket(PacketModel packetModel) { for (String key : packetModel.keySet()) { Log.i("s:", "" + key + "=" + packetModel.get(key)); } } void updateGps(int id, int lat, int lng) { PlayerObj updatePlayer = players.get(id); if(updatePlayer==null) updatePlayer=addPlayer(id, lat,lng); GeoPoint newPoint = new GeoPoint(lat, lng); updatePlayer.animateTo(newPoint); } PlayerObj addPlayer(int id, int lat, int lng) { PlayerObj newPlayer = new PlayerObj(main, main.mapView); newPlayer.srcZoom = player.srcZoom; GeoPoint newPoint = new GeoPoint(lat, lng); newPlayer.setGeoPoint(newPoint); addPlayer(id, newPlayer); return newPlayer; } void addPlayer(int id, PlayerObj newPlayer) { removePlayer(id); players.put(id, newPlayer); human.put(id, newPlayer); } void removePlayer(int id) { PlayerObj removePlayer = players.get(id); if (removePlayer != null&&removePlayer!=player) { players.remove(id); zombie.remove(id); human.remove(id); removePlayer.remove(); } } boolean canSendStartGame() { if (socket.isConnect && !socket.isConnectError && !isGameStart && gameStat.nowStatType == StatType.game) return true; else return false; } void sendStartGame() { if (canSendStartGame()) { PacketModel packetModel = new PacketModel(PacketType.startGame); socket.sendPacket(packetModel); } } PlayerObj getPlayer(int clientId) { return players.get(clientId); } void killHuman(int clientId) { PlayerObj dieHuman = getPlayer(clientId); dieHuman.setImage(PlayerObj.ImageSyle.zombie); human.remove(clientId); zombie.put(clientId, dieHuman); } GeoPoint getGeoPoint(int x, int y) { return main.mapView.getProjection().fromPixels(x, y); } Point getPoint(GeoPoint geoPoint) { Point point = new Point(); main.mapView.getProjection().toPixels(geoPoint, point); return point; } void scaleRect(Rect rect, float scaleX, float scaleY) { int w = (int) (rect.width() * scaleX / 2); int h = (int) (rect.height() * scaleY / 2); rect.set(rect.centerX() - w, rect.centerY() - h, rect.centerX() + w, rect.centerY() + h); } boolean intersects(PlayerObj playerA, PlayerObj playerB) { Rect A = playerA.getRect(); Rect B = playerB.getRect(); scaleRect(A, 0.8f, 0.8f); scaleRect(B, 0.8f, 0.8f); return Rect.intersects(A, B); } public void remove() { if (socket.isConnect) socket.close(); if (players.size() > 0) { PlayerObj[] playerArray = new PlayerObj[players.size()]; players.values().toArray(playerArray); for (PlayerObj player : playerArray) { if (player != this.player) player.remove(); } } player.setImage(PlayerObj.ImageSyle.human); main.updateSet.remove(this); main.mapView.removeView(this); main.multiplayerGameCore = null; } public void run() { switch (gameStat.nowStatType) { case ready: if (socket.isConnect) { if (System.currentTimeMillis() > sendPacketTime) { PacketModel packetModel = new PacketModel( PacketType.getClientId); packetModel .put("lat", player.geoPoint.getLatitudeE6() + ""); packetModel.put("lng", player.geoPoint.getLongitudeE6() + ""); socket.sendPacket(packetModel); sendPacketTime = System.currentTimeMillis() + 1000; } if (clientId != 0) gameStat.nowStatType = StatType.game; } if (socket.isConnectError && gameStat.isTimeOver()) remove(); break; case game: if (System.currentTimeMillis() > sendPacketTime) { if (sendLat != player.geoPoint.getLatitudeE6() && sendLng != player.geoPoint.getLongitudeE6()) { PacketModel pm = new PacketModel(PacketType.updateGps); sendLat = player.geoPoint.getLatitudeE6(); sendLng = player.geoPoint.getLongitudeE6(); pm.put("id", clientId + ""); pm.put("lat", sendLat + ""); pm.put("lng", sendLng + ""); socket.sendPacket(pm); sendPacketTime = System.currentTimeMillis() + 200; } } break; case humanWin: case zombieWin: if (gameStat.isTimeOver()) remove(); break; } } @Override protected void onDraw(Canvas canvas) { Paint paint = new Paint(); switch (gameStat.nowStatType) { case ready: readyImg.setAlpha(100); readyImg.setBounds(main.screenRect); readyImg.draw(canvas); paint.setARGB(255, 255, 255, 255); paint.setTextSize(30); paint.setTextAlign(Align.CENTER); if (!socket.isConnectError) { canvas.drawText("等待連線中", main.screenRect.centerX(), main.screenRect.centerY(), paint); } break; case game: paint.setARGB(150, 255, 255, 255); paint.setTextSize(12); if (isGameStart && gameStat.getCountdownTime() != 0) { canvas.drawText("剩餘時間:" + gameStat.getCountdownTime(), 20, 80, paint); canvas.drawText("人類數量:" + human.size(), 20, 90, paint); canvas.drawText("殭屍數量:" + zombie.size(), 20, 100, paint); } else { canvas.drawText("玩家數量:" + players.size(), 20, 80, paint); } break; case humanWin: if (gameStat.getCountdownTimeMillis() < 1000) humanWinImg.setAlpha(gameStat.getCountdownTimeMillis() / 5); else humanWinImg.setAlpha(200); humanWinImg.setBounds(main.screenRect); humanWinImg.draw(canvas); break; case zombieWin: if (gameStat.getCountdownTimeMillis() < 1000) zombieWinImg.setAlpha(gameStat.getCountdownTimeMillis() / 5); else zombieWinImg.setAlpha(200); zombieWinImg.setBounds(main.screenRect); zombieWinImg.draw(canvas); break; } if (socket.isConnectError) { paint.setARGB(255, 255, 255, 255); paint.setTextSize(30); paint.setTextAlign(Align.CENTER); canvas.drawText("連線中斷", main.screenRect.centerX(), main.screenRect .centerY(), paint); } super.onDraw(canvas); } }
程式:PlayerObj.java
package ccc.ZombieWars; import java.util.Random; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapView; import com.google.android.maps.Overlay; import com.google.android.maps.Projection; import android.content.Context; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.View; public class PlayerObj extends Overlay { Context context; MapView mapView; GeoPoint geoPoint; GeoPoint drawGeoPoint; public boolean isZombie = false; ImageSyle imageSyle = ImageSyle.human; int srcZoom; int width, height; public PlayerObj(Context context, MapView mapView) { this.context = context; this.mapView = mapView; mapView.getOverlays().add(this); setGeoPoint(new GeoPoint(mapView.getMapCenter().getLatitudeE6(), mapView.getMapCenter().getLongitudeE6())); srcZoom = mapView.getZoomLevel(); } public void remove() { mapView.getOverlays().remove(this); } enum ImageSyle { human, zombie } public void setImage(ImageSyle style) { if(style==ImageSyle.zombie){ isZombie=true; }else{ isZombie=false; } imageSyle = style; } public void animateTo(GeoPoint objPoint) { this.geoPoint = objPoint; Projection projection = mapView.getProjection(); Point dstPoint = new Point(); Point drawPoint = new Point(); projection.toPixels(geoPoint, dstPoint); projection.toPixels(drawGeoPoint, drawPoint); int dx = dstPoint.x - drawPoint.x; int dy = dstPoint.y - drawPoint.y; if (dx * dx + dy * dy > 500 * 500) { this.drawGeoPoint = geoPoint; } } public void setGeoPoint(GeoPoint objPoint) { this.drawGeoPoint = objPoint; this.geoPoint = objPoint; } Rect getRect() { Point point = new Point(); mapView.getProjection().toPixels(drawGeoPoint, point); return getRect(point.x, point.y); } private Rect getRect(int x, int y) { int width2 = width / 2; int height2 = height / 2; return new Rect(x - width2, y - height2, x + width2, y + height2); } int getWidth(){ return width; } int getHeight(){ return height; } @Override public void draw(Canvas canvas, MapView mapView, boolean shadow) { Drawable img = null; switch (imageSyle) { case human: img = context.getResources().getDrawable(R.drawable.human); break; case zombie: img = context.getResources().getDrawable(R.drawable.zombie); break; } if (img != null) { Projection projection = mapView.getProjection(); Point drawPoint = new Point(); projection.toPixels(drawGeoPoint, drawPoint); Point dstPoint = new Point(); projection.toPixels(geoPoint, dstPoint); int dx = dstPoint.x - drawPoint.x; int dy = dstPoint.y - drawPoint.y; if (dx * dx + dy * dy < 4 * 4) { this.drawGeoPoint = geoPoint; drawPoint = dstPoint; } else { Double radian = Math.atan2(dy, dx); int fixX = drawPoint.x + (int) (Math.cos(radian) * 3); int fixY = drawPoint.y + (int) (Math.sin(radian) * 3); drawGeoPoint = projection.fromPixels(fixX, fixY); } int offsetScale = mapView.getZoomLevel() - srcZoom; width = img.getIntrinsicWidth() ; height = img.getIntrinsicHeight() ; if (offsetScale < 0) { width >>= -offsetScale; height >>= -offsetScale; } else { width <<= offsetScale; height <<= offsetScale; } int width2 = width / 2; int height2 = height / 2; if(width2<5) width2=5; if(height2<5) height2=5; img.setBounds(drawPoint.x - width2, drawPoint.y - height2, drawPoint.x + width2, drawPoint.y + height2); img.draw(canvas); } super.draw(canvas, mapView, shadow); } }
程式:SingleGameCore.java
package ccc.ZombieWars; import java.util.ArrayList; import java.util.List; import java.util.Random; import ccc.ZombieWars.GameStat.StatType; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapView; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Paint.Align; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.View; public class SingleGameCore extends View implements Runnable { Random random = new Random(); GameStat gameStat; ZombieWars main; PlayerObj player; List<PlayerObj> bots = new ArrayList<PlayerObj>(); List<PlayerObj> human = new ArrayList<PlayerObj>(); List<PlayerObj> zombie = new ArrayList<PlayerObj>(); Drawable readyImg; Drawable humanWinImg; Drawable zombieWinImg; int botCount = 10; public SingleGameCore(ZombieWars context) { super(context); this.main = context; this.player = main.player; readyImg = main.getResources().getDrawable(R.drawable.ready); humanWinImg = main.getResources().getDrawable(R.drawable.humanwin); zombieWinImg = main.getResources().getDrawable(R.drawable.zombiewin); MapView.LayoutParams layout = new MapView.LayoutParams( MapView.LayoutParams.FILL_PARENT, MapView.LayoutParams.FILL_PARENT, 0, 0, MapView.LayoutParams.TOP_LEFT); gameStat = new GameStat(System.currentTimeMillis() + 3000); main.mapView.addView(this, layout); main.updateSet.add(this); } void killHuman(PlayerObj dieHuman) { dieHuman.setImage(PlayerObj.ImageSyle.zombie); human.remove(dieHuman); zombie.add(dieHuman); } GeoPoint getGeoPoint(int lat, int lng) { return new GeoPoint(lat, lng); } Point getPoint(GeoPoint geoPoint) { Point point = new Point(); main.mapView.getProjection().toPixels(geoPoint, point); return point; } void scaleRect(Rect rect, float scaleX, float scaleY) { int w = (int) (rect.width() * scaleX / 2); int h = (int) (rect.height() * scaleY / 2); rect.set(rect.centerX() - w, rect.centerY() - h, rect.centerX() + w, rect.centerY() + h); } boolean intersects(PlayerObj playerA, PlayerObj playerB) { Rect A = playerA.getRect(); Rect B = playerB.getRect(); scaleRect(A, 0.8f, 0.8f); scaleRect(B, 0.8f, 0.8f); return Rect.intersects(A, B); } public void randomBot() { int r = random.nextInt(100); if (r < botCount) { int plat,plng,blat,blng; PlayerObj bot = bots.get(r); plat=player.geoPoint.getLatitudeE6(); plng=player.geoPoint.getLongitudeE6(); blat=bot.geoPoint.getLatitudeE6(); blng=bot.geoPoint.getLongitudeE6(); Rect limRect=new Rect(plng-450,plat-450,plng+450,plat+450); if(limRect.contains(blng,blat)){ blat += random.nextInt(400) - 200; blng += random.nextInt(400) - 200; }else{ blat=plat+random.nextInt(900) - 450; blng=plng+random.nextInt(900) - 450; } bot.animateTo(getGeoPoint(blat, blng)); } } void gameInit() { for (int i = 0; i < botCount; i++) { PlayerObj bot = new PlayerObj(main, main.mapView); bot.srcZoom = player.srcZoom; int rlat, rlng; Rect playerRect = player.getRect(); do { rlat = player.geoPoint.getLatitudeE6() + random.nextInt(900) - 450; rlng = player.geoPoint.getLongitudeE6() + random.nextInt(900) - 450; GeoPoint newPoint =getGeoPoint(rlat, rlng); bot.setGeoPoint(newPoint); } while (Rect.intersects(playerRect, bot.getRect())); bots.add(bot); human.add(bot); } human.add(player); killHuman(human.get(random.nextInt(human.size()))); } public void gameUpdate() { randomBot(); if (zombie.size() > 0) { PlayerObj[] zombieArray = new PlayerObj[zombie.size()]; zombie.toArray(zombieArray); for (PlayerObj z : zombieArray) { if (human.size() > 0) { PlayerObj[] humanArray = new PlayerObj[human.size()]; human.toArray(humanArray); for (PlayerObj h : humanArray) { if (intersects(z, h)) { killHuman(h); } } } } } } public void remove() { if (bots.size() > 0) { PlayerObj[] botArray = new PlayerObj[bots.size()]; bots.toArray(botArray); for (PlayerObj bot : botArray) { bot.remove(); } } player.setImage(PlayerObj.ImageSyle.human); main.updateSet.remove(this); main.mapView.removeView(this); main.singleGameCore=null; } public void run() { switch (gameStat.nowStatType) { case ready: if (gameStat.isTimeOver()) { gameInit(); gameStat.nowStatType = StatType.game; gameStat.resetTime(System.currentTimeMillis() + 100000); } break; case game: gameUpdate(); if (gameStat.isTimeOver() || human.size() == 0) { if (player.isZombie&&human.size() == 0) gameStat.nowStatType = StatType.zombieWin; else gameStat.nowStatType = StatType.humanWin; gameStat.resetTime(System.currentTimeMillis() + 3000); } break; case humanWin: case zombieWin: if (gameStat.isTimeOver()) remove(); break; } } @Override protected void onDraw(Canvas canvas) { Paint paint = new Paint(); switch (gameStat.nowStatType) { case ready: if (gameStat.getCountdownTimeMillis() < 1000) readyImg.setAlpha(gameStat.getCountdownTimeMillis() / 10); else readyImg.setAlpha(100); readyImg.setBounds(main.screenRect); readyImg.draw(canvas); paint.setARGB(255, 255, 255, 255); paint.setTextSize(30); paint.setTextAlign(Align.CENTER); canvas .drawText(gameStat.getCountdownTime() + "秒後開始", main.screenRect.centerX(), main.screenRect.centerY()+50, paint); break; case game: paint.setARGB(150, 255, 255, 255); paint.setTextSize(12); canvas.drawText("剩餘時間:" + gameStat.getCountdownTime(), 20, 80, paint); canvas.drawText("人類數量:" + human.size(), 20, 90, paint); canvas.drawText("殭屍數量:" + zombie.size(), 20, 100, paint); break; case humanWin: if (gameStat.getCountdownTimeMillis() < 1000) humanWinImg.setAlpha(gameStat.getCountdownTimeMillis() / 5); else humanWinImg.setAlpha(200); humanWinImg.setBounds(main.screenRect); humanWinImg.draw(canvas); break; case zombieWin: if (gameStat.getCountdownTimeMillis() < 1000) zombieWinImg.setAlpha(gameStat.getCountdownTimeMillis() / 5); else zombieWinImg.setAlpha(200); zombieWinImg.setBounds(main.screenRect); zombieWinImg.draw(canvas); break; } super.onDraw(canvas); } }
程式:GameStat.java
package ccc.ZombieWars; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapView; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Paint.Align; import android.util.Log; import android.view.View; public class GameStat { private Long startTime=0l; private Long overTime=0l; private Long pauseTime=0l; private boolean isTimePause=false; StatType nowStatType=StatType.ready; enum StatType{ready,game,humanWin,zombieWin}; public GameStat(long overTime){ resetTime(overTime); } public void resetTime(long overTime){ this.startTime=System.currentTimeMillis(); this.overTime=overTime; } /** * 得到距離結束時間秒數 */ public int getCountdownTime(){ if(!isTimeOver()){ if(isTimePause) return (int)((this.overTime-pauseTime)/1000)+1; else return (int)((this.overTime-System.currentTimeMillis())/1000)+1; } else{ return 0; } } /** * 得到距離結束時間秒數 */ public int getCountdownTimeMillis(){ if(!isTimeOver()){ if(isTimePause) return (int)((this.overTime-pauseTime)); else return (int)((this.overTime-System.currentTimeMillis())); } else{ return 0; } } /** *增加時間 */ public void addTime(int addMicroseconds){ overTime+=addMicroseconds; } public boolean isTimeOver(){ if(isTimePause) return pauseTime>overTime; else return System.currentTimeMillis()>overTime; } /** * 時間暫停 */ public void timePause(){ if(!isTimePause){ pauseTime=System.currentTimeMillis(); } isTimePause=true; } /** * 得到是否為時間暫停狀態 */ public boolean isTimePause(){ return this.isTimePause; } /** * 時間繼續 */ public void timeResume(){ if(isTimePause){ overTime=System.currentTimeMillis()+overTime-pauseTime; } isTimePause=false; } }
程式:PacketHandler.java
package ccc.ZombieWars.net; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import ccc.ZombieWars.net.PacketListener; import ccc.ZombieWars.net.PacketModel; import ccc.ZombieWars.net.PacketSender; public class PacketHandler implements PacketSender { public boolean isConnect = false; protected Socket socket; public PacketHandler() { } public void listenSocket(Socket socket) { this.socket = socket; listenPacket(); } public void setPacketListener(PacketListener packetListener) { this.packetListener = packetListener; } public PacketListener getPacketListener() { return this.packetListener; } public void close() { isConnect = false; try { socket.shutdownInput(); } catch (IOException e) { } try { socket.shutdownOutput(); } catch (IOException e) { } try { socket.close(); } catch (IOException e) { } } private void listenPacket() { Thread listenThread = new Thread(new Runnable() { public void run() { while (socket.isConnected()) { isConnect = true; String dataConfig; try { BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream())); dataConfig = br.readLine(); packetListener.receivePacket(new PacketModel( dataConfig)); sleep(30); } catch (Exception e) { // System.out.println("Connect Error"); break; } } isConnect = false; packetListener.closeCallback(); } }); listenThread.start(); } public void sendPacket(PacketModel packetModel) { BufferedWriter bw; if (isConnect) { try { bw = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream())); bw.write(packetModel.toString()); bw.newLine(); bw.flush(); sleep(30); } catch (IOException e) { isConnect = false; packetListener.closeCallback(); } } } private void sleep(long millis){ try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } private PacketListener packetListener = new PacketListener() { public void receivePacket(PacketModel packetModel) { } public void closeCallback() { } }; }
程式:PacketListener.java
package ccc.ZombieWars.net; public interface PacketListener { public void receivePacket(PacketModel packetModel); public void closeCallback(); }
程式:PacketModel.java
package ccc.ZombieWars.net; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; public class PacketModel { public enum PacketType{debug,getClientId,updateGps,startGame,addPlayer,removePlayer,countdownTimeMillis,killHuman,overGame}; private PacketType packetType=null; private Map<String, String> data=new HashMap<String, String>(); public PacketModel(PacketType packetType){ setType(packetType); } public void setType(PacketType packetType){ this.packetType=packetType; data.put("packetType",packetType.toString()); } public PacketType getType(){ return packetType; } public Set<String> keySet(){ return data.keySet(); } public PacketModel(String dataConfig){ parseString(dataConfig); } public void put(String key,String value){ data.put(key, value); } public String get(String key){ return data.get(key); } public void parseString(String dataConfig){ data.clear(); //find "XXX=XXX" //matcher.group(1)="XXX" matcher.group(2)="XXX" Pattern p = Pattern.compile("([\\w]*)[\\s]*=[\\s]*([\\w]*)"); Matcher matcher = p.matcher(dataConfig); while (matcher.find()) { //EX:"NAME=XXX" //matcher.group(1)="NAME" matcher.group(2)="XXX" data.put(matcher.group(1), matcher.group(2)); } packetType=PacketType.valueOf(data.get("packetType")); } @Override public String toString() { StringBuilder stringBuilder=new StringBuilder(); data.put("packetType",packetType.toString()); for (String key : data.keySet()) { stringBuilder.append(key); stringBuilder.append("="); stringBuilder.append(data.get(key)); stringBuilder.append(" "); } return stringBuilder.toString(); } }
程式:PacketSender.java
package ccc.ZombieWars.net; public interface PacketSender { public void sendPacket(PacketModel packetModel); }
程式:ClientSocketHandler.java
package ccc.ZombieWars.net.client; import java.io.IOException; import java.net.Socket; import ccc.ZombieWars.net.PacketHandler; import ccc.ZombieWars.net.PacketModel; import ccc.ZombieWars.net.PacketModel.PacketType; public class ClientSocketHandler extends PacketHandler { public boolean isConnectError=false; public ClientSocketHandler() { } public void Connect(String host, int port) { try { socket = new Socket(host, port); socket.getInputStream(); socket.getOutputStream(); listenSocket(socket); } catch (IOException e) { isConnectError=true; } } }
AndroidManifest.xml為使用權限:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="ccc.ZombieWars"> <application android:icon="@drawable/icon" android:label="@string/app_name" > <activity android:name="ZombieWars" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <uses-library android:name="com.google.android.maps" /> </application> <uses-sdk android:minSdkVersion="4" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission." /> <!-- 取得電源控制權限 防止待命使用 --> <uses-permission android:name="android.permission.WAKE_LOCK" /> </manifest>
以下為殭屍大戰的客戶測試程式碼:
主程式:ZombieWarsClientTest.java
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.rmi.activation.ActivationGroupDesc.CommandEnvironment; import ccc.ZombieWars.net.PacketListener; import ccc.ZombieWars.net.PacketModel; import ccc.ZombieWars.net.PacketModel.PacketType; import ccc.ZombieWars.net.client.ClientSocketHandler; public class ZombieWarsClientTest { static int clientId=0; static int latE6=0; static int lngE6=0; static String serverHost="127.0.0.1"; //static String serverHost="192.168.60.8"; static int serverPort=443; static ClientSocketHandler client=new ClientSocketHandler(); public static void main(String[] args){ client.setPacketListener(new PacketListener() { @Override public void receivePacket(PacketModel packetModel) { switch(packetModel.getType()){ case getClientId: clientId=Integer.parseInt(packetModel.get("id")); break; default: printfPacket(packetModel); break; } } @Override public void closeCallback() { } }); client.Connect(serverHost, serverPort); if(!client.isConnectError){ keyLatLng(); PacketModel packetModel=new PacketModel(PacketType.getClientId); packetModel.put("lat", latE6+""); packetModel.put("lng", lngE6+""); client.sendPacket(packetModel); while(clientId==0); while(client.isConnect){ listenCommand(); } }else{ System.out.println("連線失敗"); } } static void printfPacket(PacketModel packetModel){ for (String key : packetModel.keySet()) { System.out.println("s:"+key+"="+packetModel.get(key)); } } enum CmdType {debug,exit,updateGps,startGame} static void command(CmdType cmdType){ PacketModel pm; switch (cmdType) { case debug: printf("debugValue:"); pm=new PacketModel(PacketModel.PacketType.debug); pm.put("value", readLine()); client.sendPacket(pm); break; case exit: client.close(); break; case startGame: pm=new PacketModel(PacketModel.PacketType.startGame); client.sendPacket(pm); break; case updateGps: keyLatLng(); pm=new PacketModel(PacketModel.PacketType.updateGps); pm.put("id", clientId+""); pm.put("lat", latE6+""); pm.put("lng", lngE6+""); client.sendPacket(pm); break; default: printfln("No Cmd"); break; } } static void keyLatLng(){ printf("lat:"); String latstr=readLine(); printf("lng:"); String lngstr=readLine(); try { latE6=(int) (Double.parseDouble(latstr)*1e6); } catch (Exception e) { } try { lngE6=(int) (Double.parseDouble(lngstr)*1e6); } catch (Exception e) { } } static void command(String cmd){ CmdType cmdType = null; try { cmdType = CmdType.valueOf(cmd); } catch (Exception e) { if(!cmd.equals("")) printfln("\""+cmd+"\" is error command"); return; } command(cmdType); } static void printf(String str) { System.out.print(str); } static void printfln(String str) { System.out.println(str); } static void listenCommand(){ System.out.print("Client"+clientId+"#"); command(readLine()); } static String readLine(){ BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); try { return br.readLine(); } catch (IOException e) { } return null; } }
程式:PacketHandler.java
package ccc.ZombieWars.net; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import ccc.ZombieWars.net.PacketListener; import ccc.ZombieWars.net.PacketModel; import ccc.ZombieWars.net.PacketSender; public class PacketHandler implements PacketSender { public boolean isConnect = false; protected Socket socket; public PacketHandler() { } public void listenSocket(Socket socket) { this.socket = socket; listenPacket(); } public void setPacketListener(PacketListener packetListener) { this.packetListener = packetListener; } public PacketListener getPacketListener() { return this.packetListener; } public void close() { isConnect = false; try { socket.shutdownInput(); } catch (IOException e) { } try { socket.shutdownOutput(); } catch (IOException e) { } try { socket.close(); } catch (IOException e) { } } private void listenPacket() { Thread listenThread = new Thread(new Runnable() { public void run() { while (socket.isConnected()) { isConnect = true; String dataConfig; try { BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream())); dataConfig = br.readLine(); packetListener.receivePacket(new PacketModel( dataConfig)); sleep(30); } catch (Exception e) { // System.out.println("Connect Error"); break; } } isConnect = false; packetListener.closeCallback(); } }); listenThread.start(); } public void sendPacket(PacketModel packetModel) { BufferedWriter bw; if (isConnect) { try { bw = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream())); bw.write(packetModel.toString()); bw.newLine(); bw.flush(); sleep(30); } catch (IOException e) { isConnect = false; packetListener.closeCallback(); } } } private void sleep(long millis){ try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } private PacketListener packetListener = new PacketListener() { public void receivePacket(PacketModel packetModel) { } public void closeCallback() { } }; }
程式:PacketListener.java
package ccc.ZombieWars.net; public interface PacketListener { public void receivePacket(PacketModel packetModel); public void closeCallback(); }
程式:PacketModel.java
package ccc.ZombieWars.net; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; public class PacketModel { public enum PacketType{debug,getClientId,updateGps,startGame,addPlayer,removePlayer,countdownTimeMillis,killHuman,overGame}; private PacketType packetType=null; private Map<String, String> data=new HashMap<String, String>(); public PacketModel(PacketType packetType){ setType(packetType); } public void setType(PacketType packetType){ this.packetType=packetType; data.put("packetType",packetType.toString()); } public PacketType getType(){ return packetType; } public Set<String> keySet(){ return data.keySet(); } public PacketModel(String dataConfig){ parseString(dataConfig); } public void put(String key,String value){ data.put(key, value); } public String get(String key){ return data.get(key); } public void parseString(String dataConfig){ data.clear(); //find "XXX=XXX" //matcher.group(1)="XXX" matcher.group(2)="XXX" Pattern p = Pattern.compile("([\\w]*)[\\s]*=[\\s]*([\\w]*)"); Matcher matcher = p.matcher(dataConfig); while (matcher.find()) { //EX:"NAME=XXX" //matcher.group(1)="NAME" matcher.group(2)="XXX" data.put(matcher.group(1), matcher.group(2)); } packetType=PacketType.valueOf(data.get("packetType")); } @Override public String toString() { StringBuilder stringBuilder=new StringBuilder(); data.put("packetType",packetType.toString()); for (String key : data.keySet()) { stringBuilder.append(key); stringBuilder.append("="); stringBuilder.append(data.get(key)); stringBuilder.append(" "); } return stringBuilder.toString(); } }
程式:PacketSender.java
package ccc.ZombieWars.net; public interface PacketSender { public void sendPacket(PacketModel packetModel); }
程式:ClientSocketHandler.java
package ccc.ZombieWars.net.client; import java.io.IOException; import java.net.Socket; import ccc.ZombieWars.net.PacketHandler; import ccc.ZombieWars.net.PacketModel; import ccc.ZombieWars.net.PacketModel.PacketType; public class ClientSocketHandler extends PacketHandler { public boolean isConnectError=false; public ClientSocketHandler() { } public void Connect(String host, int port) { try { socket = new Socket(host, port); socket.getInputStream(); socket.getOutputStream(); listenSocket(socket); } catch (IOException e) { isConnectError=true; } } }
以下為殭屍大戰的伺服器程式碼:
主程式:ZombieWarsServer
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.List; import java.util.ArrayList; import ccc.ZombieWars.net.PacketModel; import ccc.ZombieWars.net.PacketModel.PacketType; public class ZombieWarsServer { private static int serverport = 443; private static ServerSocket serverSocket; public static GameCore gameRoom; public static int flowId = 0; final static int roomPlayerCountMax=10; public static void main(String[] args) { try { serverSocket = new ServerSocket(serverport); System.out.println("Server is start."); startListenCommand(); gameRoom = new GameCore(); while (!serverSocket.isClosed()) { Socket newConnect = waitNewPlayer(); if (newConnect != null) { if (gameRoom.gameThread!=null||gameRoom.players.size() >= roomPlayerCountMax) { gameRoom = new GameCore(); } gameRoom.addPlayer(++flowId, newConnect); } } } catch (IOException e) { System.out.println("Server Socket ERROR"); } } public static Socket waitNewPlayer() { Socket socket = null; System.out.println("Wait new clinet connect"); try { socket = serverSocket.accept(); } catch (IOException e) { System.out.println("Socket Close"); } return socket; } enum CmdType { size, exit, debug } static void command(CmdType cmdType) { PacketModel pm; switch (cmdType) { case debug: printf("debugValue:"); pm = new PacketModel(PacketModel.PacketType.debug); pm.put("value", readLine()); gameRoom.castPacket(pm); break; case exit: try { serverSocket.close(); } catch (IOException e) { } gameRoom.closeRoom(); break; case size: printfln("RoomPresonCount:" + gameRoom.players.size()); break; default: printfln("No Cmd"); break; } } static void command(String cmd) { CmdType cmdType = null; try { cmdType = CmdType.valueOf(cmd); } catch (Exception e) { if(!cmd.equals("")) printfln("\""+cmd+"\" is error command"); return; } command(cmdType); } static void printf(String str) { System.out.print(str); } static void printfln(String str) { System.out.println(str); } static void startListenCommand() { Thread cmdThread = new Thread(new Runnable() { @Override public void run() { while (!serverSocket.isClosed()) { listenCommand(); } } }); cmdThread.start(); } static void listenCommand() { System.out.print("Server#"); command(readLine()); } static String readLine() { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); try { return br.readLine(); } catch (IOException e) { } return null; } }
程式:GameCore.java
import java.awt.Rectangle; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import ccc.ZombieWars.net.PacketHandler; import ccc.ZombieWars.net.PacketListener; import ccc.ZombieWars.net.PacketModel; import ccc.ZombieWars.net.PacketModel.PacketType; import ccc.ZombieWars.server.ServerSocketHandler; public class GameCore { public Map<PacketListener, Integer> playerIds = new HashMap<PacketListener, Integer>(); public Map<Integer, ServerSocketHandler> players = new HashMap<Integer, ServerSocketHandler>(); public List<ServerSocketHandler> zombie = new ArrayList<ServerSocketHandler>(); public List<ServerSocketHandler> human = new ArrayList<ServerSocketHandler>(); public GameStat gameStat; public Thread gameThread; Random random = new Random(); public GameCore() { } public void gameStart() { ServerSocketHandler[] tmp = new ServerSocketHandler[players.size()]; players.values().toArray(tmp); for (ServerSocketHandler serverSocketHandler : tmp) { if (!serverSocketHandler.isGetId) return; } gameThread = new Thread(gameUpdate); gameThread.start(); } public Runnable gameUpdate = new Runnable() { long sendPacketTime=0; @Override public void run() { PacketModel pm; pm = new PacketModel(PacketType.startGame); castPacket(pm); sleep(100); gameStat = new GameStat(System.currentTimeMillis() + 100000); int r = random.nextInt(human.size()); killHuman(human.get(r)); sleep(50); while (!gameStat.isTimeOver() && !(human.size() == 0 || zombie.size() == 0)) { if(System.currentTimeMillis()>sendPacketTime){ pm = new PacketModel(PacketType.countdownTimeMillis); pm.put("value", gameStat.getCountdownTimeMillis() + ""); castPacket(pm); sendPacketTime=System.currentTimeMillis()+3000; } sleep(50); checkKillHuman(); } sleep(100); if (human.size() == 0) { pm = new PacketModel(PacketType.overGame); pm.put("win", "zombie"); } else { pm = new PacketModel(PacketType.overGame); pm.put("win", "human"); } castPacket(pm); sleep(5000); closeRoom(); } }; void checkKillHuman() { if (zombie.size() > 0) { ServerSocketHandler[] zombieArray = new ServerSocketHandler[zombie .size()]; zombie.toArray(zombieArray); for (ServerSocketHandler z : zombieArray) { if (human.size() > 0) { ServerSocketHandler[] humanArray = new ServerSocketHandler[human .size()]; human.toArray(humanArray); for (ServerSocketHandler h : humanArray) { Rectangle zRect = getRect(z.gpoint.X, z.gpoint.Y); Rectangle hRect = getRect(h.gpoint.X, h.gpoint.Y); if (zRect.intersects(hRect)) { killHuman(h); } } } } } } Rectangle getRect(int x, int y) { int width = 30; int height = 30; return new Rectangle(x, y, width, height); } void sleep(long milis) { try { Thread.sleep(milis); } catch (InterruptedException e) { e.printStackTrace(); } } void killHuman(int dieHumanId) { killHuman(getPlayer(dieHumanId)); } void killHuman(ServerSocketHandler dieHuman) { dieHuman.isZombie = true; human.remove(dieHuman); zombie.add(dieHuman); PacketModel pm = new PacketModel(PacketType.killHuman); pm.put("id", dieHuman.clientId + ""); castPacket(pm); } PacketListener getNewPacketListener() { return new PacketListener() { @Override public void receivePacket(PacketModel packetModel) { ServerSocketHandler player = getPlayer(this); switch (packetModel.getType()) { case getClientId: player.updateGps(packetModel.get("lat"), packetModel .get("lng")); player.sendClientId(); PacketModel pm = new PacketModel(PacketType.addPlayer); pm.put("id", player.clientId + ""); pm.put("lat", player.getlatE6() + ""); pm.put("lng", player.getlngE6() + ""); castPacket(player, pm); sleep(100); ServerSocketHandler[] allPlayer = new ServerSocketHandler[players .size()]; players.values().toArray(allPlayer); for (ServerSocketHandler otherPlayer : allPlayer) { if (otherPlayer != player && otherPlayer.isGetId) { sleep(100); pm = new PacketModel(PacketType.addPlayer); pm.put("id", otherPlayer.clientId + ""); pm.put("lat", otherPlayer.getlatE6() + ""); pm.put("lng", otherPlayer.getlngE6() + ""); player.sendPacket(pm); } } player.isGetId = true; break; case startGame: if (players.size() > 1) { gameStart(); } break; case updateGps: castPacket(player, packetModel); sleep(100); player.updateGps(packetModel.get("lat"), packetModel .get("lng")); break; default: printfPacket(player.clientId, packetModel); break; } } @Override public void closeCallback() { removePlayer(this); } }; } void addPlayer(int clientId, Socket socket) { ServerSocketHandler player = new ServerSocketHandler(clientId); PacketListener packetListener = getNewPacketListener(); player.setPacketListener(packetListener); players.put(clientId, player); playerIds.put(packetListener, clientId); human.add(player); player.listenSocket(socket); } void castPacket(PacketModel packetModel) { ServerSocketHandler[] tmp = new ServerSocketHandler[players.size()]; players.values().toArray(tmp); for (ServerSocketHandler serverSocketHandler : tmp) { if (serverSocketHandler.isGetId) serverSocketHandler.sendPacket(packetModel); } } void castPacket(ServerSocketHandler sendPlayer, PacketModel packetModel) { ServerSocketHandler[] tmp = new ServerSocketHandler[players.size()]; players.values().toArray(tmp); for (ServerSocketHandler serverSocketHandler : tmp) { if (serverSocketHandler != sendPlayer && serverSocketHandler.isGetId) serverSocketHandler.sendPacket(packetModel); } } void closeRoom() { ServerSocketHandler[] tmp = new ServerSocketHandler[players.size()]; players.values().toArray(tmp); for (ServerSocketHandler serverSocketHandler : tmp) { serverSocketHandler.close(); } } ServerSocketHandler getPlayer(PacketListener packetListener) { return players.get(playerIds.get(packetListener)); } ServerSocketHandler getPlayer(int playerId) { return players.get(playerId); } void removePlayer(PacketListener packetListener) { int clientId = playerIds.get(packetListener); removePlayer(clientId); } void removePlayer(int clientId) { ServerSocketHandler player = players.get(clientId); PacketModel pm = new PacketModel(PacketType.removePlayer); pm.put("id", clientId + ""); castPacket(player, pm); if (player != null) { playerIds.remove(player.getPacketListener()); players.remove(player.clientId); zombie.remove(player); human.remove(player); player.close(); } } void printfPacket(int ClientId, PacketModel packetModel) { for (String key : packetModel.keySet()) { System.out.println("c" + ClientId + ":" + key + "=" + packetModel.get(key)); } } }
程式:GameStat.java
public class GameStat { private Long startTime=0l; private Long overTime=0l; private Long pauseTime=0l; private boolean isTimePause=false; StatType nowStatType=StatType.ready; enum StatType{ready,game,win,lose}; public GameStat(long overTime){ resetTime(overTime); } public void resetTime(long overTime){ this.startTime=System.currentTimeMillis(); this.overTime=overTime; } /** * 得到距離結束時間秒數 */ public int getCountdownTime(){ if(!isTimeOver()){ if(isTimePause) return (int)((this.overTime-pauseTime)/1000)+1; else return (int)((this.overTime-System.currentTimeMillis())/1000)+1; } else{ return 0; } } /** * 得到距離結束時間秒數 */ public int getCountdownTimeMillis(){ if(!isTimeOver()){ if(isTimePause) return (int)((this.overTime-pauseTime)); else return (int)((this.overTime-System.currentTimeMillis())); } else{ return 0; } } /** *增加時間 */ public void addTime(int addMicroseconds){ overTime+=addMicroseconds; } public boolean isTimeOver(){ if(isTimePause) return pauseTime>overTime; else return System.currentTimeMillis()>overTime; } /** * 時間暫停 */ public void timePause(){ if(!isTimePause){ pauseTime=System.currentTimeMillis(); } isTimePause=true; } /** * 得到是否為時間暫停狀態 */ public boolean isTimePause(){ return this.isTimePause; } /** * 時間繼續 */ public void timeResume(){ if(isTimePause){ overTime=System.currentTimeMillis()+overTime-pauseTime; } isTimePause=false; } }
程式:PacketHandler.java
package ccc.ZombieWars.net; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import ccc.ZombieWars.net.PacketListener; import ccc.ZombieWars.net.PacketModel; import ccc.ZombieWars.net.PacketSender; public class PacketHandler implements PacketSender { public boolean isConnect = false; protected Socket socket; public PacketHandler() { } public void listenSocket(Socket socket) { this.socket = socket; listenPacket(); } public void setPacketListener(PacketListener packetListener) { this.packetListener = packetListener; } public PacketListener getPacketListener() { return this.packetListener; } public void close() { isConnect = false; try { socket.shutdownInput(); } catch (IOException e) { } try { socket.shutdownOutput(); } catch (IOException e) { } try { socket.close(); } catch (IOException e) { } } private void listenPacket() { Thread listenThread = new Thread(new Runnable() { public void run() { while (socket.isConnected()) { isConnect = true; String dataConfig; try { BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream())); dataConfig = br.readLine(); packetListener.receivePacket(new PacketModel( dataConfig)); sleep(30); } catch (Exception e) { // System.out.println("Connect Error"); break; } } isConnect = false; packetListener.closeCallback(); } }); listenThread.start(); } public void sendPacket(PacketModel packetModel) { BufferedWriter bw; if (isConnect) { try { bw = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream())); bw.write(packetModel.toString()); bw.newLine(); bw.flush(); sleep(30); } catch (IOException e) { isConnect = false; packetListener.closeCallback(); } } } private void sleep(long millis){ try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } private PacketListener packetListener = new PacketListener() { public void receivePacket(PacketModel packetModel) { } public void closeCallback() { } }; }
程式:PacketListener.java
package ccc.ZombieWars.net; public interface PacketListener { public void receivePacket(PacketModel packetModel); public void closeCallback(); }
程式:PacketModel.java
package ccc.ZombieWars.net; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; public class PacketModel { public enum PacketType{debug,getClientId,updateGps,startGame,addPlayer,removePlayer,countdownTimeMillis,killHuman,overGame}; private PacketType packetType=null; private Map<String, String> data=new HashMap<String, String>(); public PacketModel(PacketType packetType){ setType(packetType); } public void setType(PacketType packetType){ this.packetType=packetType; data.put("packetType",packetType.toString()); } public PacketType getType(){ return packetType; } public Set<String> keySet(){ return data.keySet(); } public PacketModel(String dataConfig){ parseString(dataConfig); } public void put(String key,String value){ data.put(key, value); } public String get(String key){ return data.get(key); } public void parseString(String dataConfig){ data.clear(); //find "XXX=XXX" //matcher.group(1)="XXX" matcher.group(2)="XXX" Pattern p = Pattern.compile("([\\w]*)[\\s]*=[\\s]*([\\w]*)"); Matcher matcher = p.matcher(dataConfig); while (matcher.find()) { //EX:"NAME=XXX" //matcher.group(1)="NAME" matcher.group(2)="XXX" data.put(matcher.group(1), matcher.group(2)); } packetType=PacketType.valueOf(data.get("packetType")); } @Override public String toString() { StringBuilder stringBuilder=new StringBuilder(); data.put("packetType",packetType.toString()); for (String key : data.keySet()) { stringBuilder.append(key); stringBuilder.append("="); stringBuilder.append(data.get(key)); stringBuilder.append(" "); } return stringBuilder.toString(); } }
程式:PacketSender.java
package ccc.ZombieWars.net; public interface PacketSender { public void sendPacket(PacketModel packetModel); }
程式:ServerSocketHandler.java
package ccc.ZombieWars.server; import gmap.GLatLng; import gmap.GPoint; import ccc.ZombieWars.net.PacketHandler; import ccc.ZombieWars.net.PacketListener; import ccc.ZombieWars.net.PacketModel; import ccc.ZombieWars.net.PacketModel.PacketType; public class ServerSocketHandler extends PacketHandler { public int clientId = 0; private int latE6 = 0; private int lngE6 = 0; public gmap.GPoint gpoint = new GPoint(0, 0, 19); public boolean isGetId = false; public boolean isZombie = false; public ServerSocketHandler(int clientId) { this.clientId = clientId; } public void sendClientId() { // System.out.print("sendClientId"+clientId); PacketModel packetModel = new PacketModel(PacketType.getClientId); packetModel.put("id", String.valueOf(clientId)); super.sendPacket(packetModel); } public int getlatE6() { return latE6; } public int getlngE6() { return lngE6; } public void updateGps(String strLatE6, String strLngE6) { try { this.latE6 = Integer.parseInt(strLatE6); } catch (Exception e) { } try { this.lngE6 = Integer.parseInt(strLngE6); } catch (Exception e) { } double lat = latE6 / 1e6; double lng = lngE6 / 1e6; gpoint = new GPoint(lat, lng,19); } }
程式:GLatLng.java
package gmap; public class GLatLng { public double lat; public double lng; public GLatLng(double lat,double lng){ this.lat=lat; this.lng=lng; } public GLatLng(int x,int y,int zoom){ this.lng= MercatorProjection.XToLng(x, zoom); this.lat= MercatorProjection.YToLat(y, zoom); } public GLatLng(GPoint point){ this.lng= MercatorProjection.XToLng(point.X,point.Zoom); this.lat= MercatorProjection.YToLat(point.Y,point.Zoom); } public GLatLng(GLatLng latlng){ this.lng= latlng.lng; this.lat= latlng.lat; } public GPoint getGPoint(int zoom){ return new GPoint(lat, lng, zoom); } }
程式:GPoint.java
package gmap; public class GPoint { public int X; public int Y; public int Zoom; public GPoint(double lat,double lng,int zoom){ this.X=MercatorProjection.LngToX(lng, zoom); this.Y=MercatorProjection.LatToY(lat, zoom); this.Zoom=zoom; } public GPoint(int x,int y,int zoom){ this.X = x; this.Y = y; this.Zoom = zoom; } public GPoint(GLatLng latlng, int zoom) { this.X=MercatorProjection.LngToX(latlng.lng, zoom); this.Y=MercatorProjection.LatToY(latlng.lat, zoom); this.Zoom=zoom; } public GPoint(GPoint point){ this.X = point.X; this.Y = point.Y; this.Zoom = point.Zoom; } // 位移 public void offset(int dx,int dy){ this.X += dx; this.Y += dy; } // 取得經緯度 public GLatLng getGLatLng(){ return new GLatLng(X, Y, Zoom); } // 轉字串 public String toString(){ return String.format("GX=%1$s GY=%2$s GZ=%3$s", X, Y, Zoom); } // 設定縮放 public void setZoom(int zoom) { GLatLng latlng = new GLatLng(this); this.X = MercatorProjection.LngToX(latlng.lng, zoom); this.Y = MercatorProjection.LatToY(latlng.lat, zoom); this.Zoom = zoom; } }
程式:MercatorProjection.java
package gmap; public class MercatorProjection { // 經度轉成點(point) public static int LngToX(double Longitude,int zoom) { int numberOfTiles = 1 << zoom << 8; return (int)((Longitude + 180.0) * numberOfTiles / 360.0); } // 緯度轉成點(point) public static int LatToY(double Latitude,int zoom) { int numberOfTiles = 1 << zoom << 8; double projection = Math.log(Math.tan(Math.PI / 4 + ((Latitude / 180 * Math.PI) / 2))); double y = (projection / Math.PI); y = 1.0 - y; y = y / 2.0 * numberOfTiles; return (int)y; } // 點(X)轉成經度 public static double XToLng(int pointX,int zoom) { int numberOfTiles = 1 << zoom << 8; return (pointX * (360.0 / numberOfTiles)) - 180.0; } // 點(Y)轉成緯度 public static double YToLat(int pointY,int zoom) { int numberOfTiles = 1 << zoom << 8; double Latitude = pointY * (2.0 / numberOfTiles); Latitude = 1 - Latitude; Latitude = Latitude * Math.PI; Latitude = Math.atan(Math.sinh(Latitude)) * 180.0 / Math.PI; return Latitude; } }
程式手機操作影片
程式手機測試影片
程式影片解說
Post preview:
Close preview