├── .classpath ├── .gitignore ├── .project ├── .settings └── org.eclipse.core.resources.prefs ├── README.md ├── data ├── Supermario_BGM_Overworld.mp3 ├── config.yml ├── img │ ├── background_0000.gif │ ├── block_0000.gif │ ├── brick_0000.gif │ ├── bw_mario_falling_0000.gif │ ├── bw_mario_falling_0001.gif │ ├── bw_mario_jumping_0000.gif │ ├── bw_mario_running_0000.gif │ ├── bw_mario_running_0001.gif │ ├── bw_mario_running_0002.gif │ ├── bw_mario_running_0003.gif │ ├── bw_mario_standing_0000.gif │ ├── gold_0000.gif │ ├── gold_0001.gif │ ├── gold_0002.gif │ ├── gold_0003.gif │ ├── ground_0000.gif │ ├── ground_filler_0000.gif │ ├── mario_falling_0000.gif │ ├── mario_falling_0001.gif │ ├── mario_jumping_0000.gif │ ├── mario_running_0000.gif │ ├── mario_running_0001.gif │ ├── mario_running_0002.gif │ ├── mario_running_0003.gif │ ├── mario_standing_0000.gif │ ├── princess_standing_0000.gif │ └── question_0000.gif └── map.txt ├── lib ├── core.jar ├── gluegen-rt-natives-linux-amd64.jar ├── gluegen-rt-natives-windows-amd64.jar ├── gluegen-rt.jar ├── gson-2.8.0.jar ├── jeromq-0.3.5.jar ├── jl1.0.1.jar ├── jogl-all-natives-linux-amd64.jar ├── jogl-all-natives-linux-armv6hf.jar ├── jogl-all-natives-windows-amd64.jar ├── jogl-all.jar ├── jsminim.jar ├── minim.jar ├── mp3spi1.9.5.jar ├── snakeyaml-1.17.jar ├── tritonus_aos.jar └── tritonus_share.jar ├── pde └── MarioMultiplay │ ├── MarioMultiplay.pde │ ├── code │ ├── gson-2.8.0.jar │ ├── jeromq-0.3.5.jar │ └── snakeyaml-1.17.jar │ └── data │ ├── Supermario_BGM_Overworld.mp3 │ ├── config.yml │ ├── img │ ├── background_0000.gif │ ├── block_0000.gif │ ├── brick_0000.gif │ ├── bw_mario_falling_0000.gif │ ├── bw_mario_falling_0001.gif │ ├── bw_mario_jumping_0000.gif │ ├── bw_mario_running_0000.gif │ ├── bw_mario_running_0001.gif │ ├── bw_mario_running_0002.gif │ ├── bw_mario_running_0003.gif │ ├── bw_mario_standing_0000.gif │ ├── gold_0000.gif │ ├── gold_0001.gif │ ├── gold_0002.gif │ ├── gold_0003.gif │ ├── ground_0000.gif │ ├── ground_filler_0000.gif │ ├── mario_falling_0000.gif │ ├── mario_falling_0001.gif │ ├── mario_jumping_0000.gif │ ├── mario_running_0000.gif │ ├── mario_running_0001.gif │ ├── mario_running_0002.gif │ ├── mario_running_0003.gif │ ├── mario_standing_0000.gif │ ├── princess_standing_0000.gif │ └── question_0000.gif │ └── map.txt ├── server └── Program.cs └── src └── MarioMultiplay.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | MarioMultiplayer 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/MarioMultiplay.java=UTF-8 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Mario Multiplayer 2 | ================= 3 | 4 | ![Game Intro GIF](https://coolspeed.files.wordpress.com/2017/04/enemycd.gif?w=630) 5 | 6 | Supermario multiplayer game, written in Java ([Processing](https://processing.org/) Java Mode) using [ZeroMQ](http://zeromq.org/) ([JeroMQ](https://github.com/zeromq/jeromq)). 7 | 8 | This repo also includes a server which is written in C# using ZeroMQ ([NetMQ](https://github.com/zeromq/netmq)). 9 | 10 | It's netcode is based on [State Synchronization](http://gafferongames.com/networked-physics/state-synchronization/). 11 | 12 | The client has [1500 lines of Java code in total](https://github.com/coolspeed/MarioMultiplayer/blob/master/src/MarioMultiplay.java), and the server [22 lines of C# code](https://github.com/coolspeed/MarioMultiplayer/blob/master/server/Program.cs). 13 | 14 | This project is developped as a team joining homework of [What! Studio](https://github.com/what-studio). 15 | 16 | ## Game rule 17 | 18 | The mario who first steps over the other wins. 19 | 20 | ## Design philosophy 21 | 22 | Described in my blog (in Korean): 23 | 24 | https://coolspeed.wordpress.com/2017/04/11/supermario_multiplayer_postmortem/ 25 | -------------------------------------------------------------------------------- /data/Supermario_BGM_Overworld.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/Supermario_BGM_Overworld.mp3 -------------------------------------------------------------------------------- /data/config.yml: -------------------------------------------------------------------------------- 1 | room_num: "123456" 2 | my_mario_num: 0 3 | my_frictional_force: 0.7 4 | server_host: 127.0.0.1 5 | show_packet_indicator: true 6 | packet_frugal: true 7 | -------------------------------------------------------------------------------- /data/img/background_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/background_0000.gif -------------------------------------------------------------------------------- /data/img/block_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/block_0000.gif -------------------------------------------------------------------------------- /data/img/brick_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/brick_0000.gif -------------------------------------------------------------------------------- /data/img/bw_mario_falling_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_falling_0000.gif -------------------------------------------------------------------------------- /data/img/bw_mario_falling_0001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_falling_0001.gif -------------------------------------------------------------------------------- /data/img/bw_mario_jumping_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_jumping_0000.gif -------------------------------------------------------------------------------- /data/img/bw_mario_running_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_running_0000.gif -------------------------------------------------------------------------------- /data/img/bw_mario_running_0001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_running_0001.gif -------------------------------------------------------------------------------- /data/img/bw_mario_running_0002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_running_0002.gif -------------------------------------------------------------------------------- /data/img/bw_mario_running_0003.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_running_0003.gif -------------------------------------------------------------------------------- /data/img/bw_mario_standing_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/bw_mario_standing_0000.gif -------------------------------------------------------------------------------- /data/img/gold_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/gold_0000.gif -------------------------------------------------------------------------------- /data/img/gold_0001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/gold_0001.gif -------------------------------------------------------------------------------- /data/img/gold_0002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/gold_0002.gif -------------------------------------------------------------------------------- /data/img/gold_0003.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/gold_0003.gif -------------------------------------------------------------------------------- /data/img/ground_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/ground_0000.gif -------------------------------------------------------------------------------- /data/img/ground_filler_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/ground_filler_0000.gif -------------------------------------------------------------------------------- /data/img/mario_falling_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_falling_0000.gif -------------------------------------------------------------------------------- /data/img/mario_falling_0001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_falling_0001.gif -------------------------------------------------------------------------------- /data/img/mario_jumping_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_jumping_0000.gif -------------------------------------------------------------------------------- /data/img/mario_running_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_running_0000.gif -------------------------------------------------------------------------------- /data/img/mario_running_0001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_running_0001.gif -------------------------------------------------------------------------------- /data/img/mario_running_0002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_running_0002.gif -------------------------------------------------------------------------------- /data/img/mario_running_0003.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_running_0003.gif -------------------------------------------------------------------------------- /data/img/mario_standing_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/mario_standing_0000.gif -------------------------------------------------------------------------------- /data/img/princess_standing_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/princess_standing_0000.gif -------------------------------------------------------------------------------- /data/img/question_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/data/img/question_0000.gif -------------------------------------------------------------------------------- /data/map.txt: -------------------------------------------------------------------------------- 1 | BBBBBBBBBBBBBBBBBBBBBB 2 | BBBBBBBBBBBBBBBBBBBBBB 3 | BBBBBB BBBBBBBBBBBBBBBBBBBBBB 4 | BBBBBBBBBBBBBBBBBBBBBB 5 | RRRRR RRRRRR BBBBBBBBBBBBBBBBBBBBBB 6 | BBBBBBBBBBBBBBBBBBBBBB 7 | BBBBBB BBBBBBBBBBBBBBBBBBBBBB 8 | BBBBBBBBBBBBBBBBBBBBBB 9 | RRRR RRRRR OO RRR BBBBBBBBBBBBBBBBBBBBBB 10 | O O RRRBBBBBBBBBBBBBBBBBBBBBB 11 | BBBBBBBBBBBBBBBBBBBBBB 12 | B BBBBBBBBBBBBBBBBBBBBBB 13 | #####################################################################BBBBBBBBBBBBBBBBBBBBB 14 | #####################################################################BBBBBBBBBBBBBBBBBBBBB 15 | -------------------------------------------------------------------------------- /lib/core.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/core.jar -------------------------------------------------------------------------------- /lib/gluegen-rt-natives-linux-amd64.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/gluegen-rt-natives-linux-amd64.jar -------------------------------------------------------------------------------- /lib/gluegen-rt-natives-windows-amd64.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/gluegen-rt-natives-windows-amd64.jar -------------------------------------------------------------------------------- /lib/gluegen-rt.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/gluegen-rt.jar -------------------------------------------------------------------------------- /lib/gson-2.8.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/gson-2.8.0.jar -------------------------------------------------------------------------------- /lib/jeromq-0.3.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jeromq-0.3.5.jar -------------------------------------------------------------------------------- /lib/jl1.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jl1.0.1.jar -------------------------------------------------------------------------------- /lib/jogl-all-natives-linux-amd64.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jogl-all-natives-linux-amd64.jar -------------------------------------------------------------------------------- /lib/jogl-all-natives-linux-armv6hf.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jogl-all-natives-linux-armv6hf.jar -------------------------------------------------------------------------------- /lib/jogl-all-natives-windows-amd64.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jogl-all-natives-windows-amd64.jar -------------------------------------------------------------------------------- /lib/jogl-all.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jogl-all.jar -------------------------------------------------------------------------------- /lib/jsminim.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/jsminim.jar -------------------------------------------------------------------------------- /lib/minim.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/minim.jar -------------------------------------------------------------------------------- /lib/mp3spi1.9.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/mp3spi1.9.5.jar -------------------------------------------------------------------------------- /lib/snakeyaml-1.17.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/snakeyaml-1.17.jar -------------------------------------------------------------------------------- /lib/tritonus_aos.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/tritonus_aos.jar -------------------------------------------------------------------------------- /lib/tritonus_share.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/lib/tritonus_share.jar -------------------------------------------------------------------------------- /pde/MarioMultiplay/MarioMultiplay.pde: -------------------------------------------------------------------------------- 1 | import ddf.minim.*; 2 | import java.util.Map; 3 | import java.util.Set; 4 | import java.util.HashSet; 5 | import java.util.ArrayList; 6 | import java.util.LinkedList; 7 | import java.lang.Math; 8 | import org.zeromq.ZMQ; 9 | import org.zeromq.ZMQ.Context; 10 | import org.zeromq.ZMQ.Socket; 11 | import com.google.gson.Gson; 12 | import java.util.concurrent.ConcurrentLinkedQueue; 13 | import org.yaml.snakeyaml.Yaml; 14 | 15 | 16 | 17 | 18 | public void settings() { 19 | size(WIDTH, HEIGHT, P2D); 20 | } 21 | 22 | 23 | 24 | 25 | 26 | enum GAME_STATE { 27 | PLAYING, 28 | WIN, 29 | LOSE 30 | } 31 | 32 | // Global constants 33 | 34 | int FRAME_RATE = 60; 35 | int FRAMES_PER_SHOT = FRAME_RATE / 10; 36 | int WIDTH = 640; 37 | int HEIGHT = 432; 38 | int ASSET_SCALE = 2; 39 | float GRAVITY = 0.7f; 40 | float MARIO_JUMP_FORCE = 12f; 41 | float BOOST_JUMP_FORCE = 4.8f; 42 | float MARIO_VX_LIMIT = 4.7f; 43 | float MARIO_FORCE = 0.2f; 44 | int CAMERA_RANGE_LEFT = 170; 45 | int BLOCK_OFFSET = -16; 46 | String SERVER_HOST; 47 | String XSUB_PORT = "1234"; 48 | String XPUB_PORT = "5678"; 49 | int MY_MARIO_NUM; 50 | String ROOM_NUM; 51 | float MY_FRICTIONAL_FORCE = 0.4f; // 정상 0.4. 얼음 세상: 0.1 52 | boolean SHOW_PACKET_INDICATOR; 53 | 54 | // Global variables 55 | 56 | GAME_STATE gameState = GAME_STATE.PLAYING; 57 | long frame = 0; 58 | Set pendingKeyboardEvents; 59 | boolean needSync = true; 60 | 61 | // Global objects 62 | 63 | Mario marioMe; 64 | Mario marioRival; 65 | Mario mario0; 66 | Mario mario1; 67 | Sprite bgImgSprite; 68 | Sprite princessSprite; 69 | BlockManager blockManager; 70 | CollisionDetector collisionDetector; 71 | EnemyCollisionDetector enemyCD; 72 | NetworkManager networkManager; 73 | UIManger uiManager; 74 | // Audio ------------- START 75 | AudioPlayer player; 76 | Minim minim; // Audio context 77 | // Audio ------------- END 78 | Gson gson; 79 | 80 | Context pubContext; 81 | Socket pubSocket; 82 | ConcurrentLinkedQueue msgQueue; 83 | 84 | 85 | public void setup() { 86 | background(51); 87 | frameRate(FRAME_RATE); 88 | 89 | // Config file 90 | Yaml yaml = new Yaml(); 91 | String[] lines = loadStrings("config.yml"); 92 | StringBuilder sb = new StringBuilder(); 93 | for (String line: lines) sb.append(line).append("\n"); 94 | String document = sb.toString(); 95 | Map confMap = (Map) yaml.load(document); 96 | ROOM_NUM = (String) confMap.get("room_num"); 97 | System.out.println("ROOM_NUM=" + ROOM_NUM); 98 | MY_MARIO_NUM = (int) confMap.get("my_mario_num"); 99 | System.out.println("MY_MARIO_NUM=" + MY_MARIO_NUM); 100 | MY_FRICTIONAL_FORCE = (float) (double) confMap.get("my_frictional_force"); 101 | System.out.println("MY_FRICTIONAL_FORCE=" + MY_FRICTIONAL_FORCE); 102 | SERVER_HOST = (String) confMap.get("server_host"); 103 | System.out.println("SERVER_HOST=" + SERVER_HOST); 104 | SHOW_PACKET_INDICATOR = (boolean) confMap.get("show_packet_indicator"); 105 | System.out.println("SHOW_PACKET_INDICATOR=" + SHOW_PACKET_INDICATOR); 106 | 107 | // Backgroud image 108 | bgImgSprite = new SharedSprite("img/background_", 1); 109 | 110 | // Princess 111 | princessSprite = new SharedSprite("img/princess_standing_", 1); 112 | 113 | // Block manager 114 | blockManager = new BlockManager(); 115 | 116 | // Collision detector 117 | collisionDetector = new CollisionDetector(); 118 | enemyCD = new EnemyCollisionDetector(); 119 | 120 | // Well, our main character appears 121 | marioMe = new Mario(false); // Color 122 | marioRival = new Mario(true); // Black and White 123 | 124 | // marioMe, marioRival 125 | if (MY_MARIO_NUM == 0) { 126 | mario0 = marioMe; 127 | mario1 = marioRival; 128 | } else { 129 | mario1 = marioMe; 130 | mario0 = marioRival; 131 | } 132 | // 마리오1은 마리오0의 오른쪽에 133 | mario1.x = mario0.x + 300; 134 | 135 | // BGM 136 | minim = new Minim(this); 137 | player = minim.loadFile("Supermario_BGM_Overworld.mp3"); 138 | player.play(); 139 | 140 | // Network manager 141 | networkManager = new NetworkManager(); 142 | 143 | // UI manager 144 | uiManager = new UIManger(); 145 | 146 | // Gson 147 | gson = new Gson(); 148 | 149 | // Publisher 150 | pubContext = ZMQ.context(1); 151 | pubSocket = pubContext.socket(ZMQ.PUB); 152 | pubSocket.connect("tcp://" + SERVER_HOST + ":" + XSUB_PORT); 153 | 154 | // Message queue 155 | msgQueue = new ConcurrentLinkedQueue(); 156 | 157 | // Keyboard event queue 158 | pendingKeyboardEvents = new HashSet(); 159 | 160 | // Subscriber thread: 꼭 msgQueue 가 초기화 된 후에 구독자 스레드를 론칭 시켜야! 161 | new Thread(new Subscriber()).start(); 162 | } 163 | 164 | 165 | public void stop() { 166 | // Audio player 167 | player.close(); 168 | minim.stop(); 169 | 170 | // ZeroMQ publisher 171 | pubSocket.close(); 172 | pubContext.term(); 173 | 174 | super.stop(); 175 | } 176 | 177 | 178 | public void draw() { 179 | processUserInput(); 180 | 181 | processGameLogic(); 182 | 183 | doNetworkStuff(); 184 | 185 | uiManager.update(); 186 | 187 | render(); 188 | 189 | ++frame; 190 | } 191 | 192 | 193 | // ============================= USER INPUT ================================ 194 | 195 | 196 | public void processUserInput() { 197 | marioMe.processKeyboard(); 198 | } 199 | 200 | /** 201 | * Called once every time a key is pressed. The key that was pressed is 202 | * stored in the key variable. 203 | */ 204 | public void keyPressed() { 205 | marioMe.onKeyPress(key); 206 | } 207 | 208 | /** 209 | * Called once every time a key is released. The key that was released will 210 | * be stored in the key variable. 211 | */ 212 | public void keyReleased() { 213 | marioMe.onKeyRelease(key); 214 | } 215 | 216 | 217 | // ============================= GAME LOGIC ================================ 218 | 219 | 220 | public void processGameLogic() { 221 | // Game over? 222 | if (gameState != GAME_STATE.PLAYING) return; 223 | 224 | // Update the shared sprites 225 | blockManager.spriteGold.update(); 226 | 227 | // Do the simulation 228 | marioMe.update(); 229 | marioRival.update(); 230 | } 231 | 232 | 233 | // ======================== NETWORK COMMUNICATION =========================== 234 | 235 | 236 | public void doNetworkStuff() { 237 | networkWrite(); // 먼저 조립된 내 마리오 상태 보내고 238 | networkRead(); // 고스트 마리오를 세팅하자 239 | } 240 | 241 | public void networkRead() { 242 | // Game over? 243 | if (gameState != GAME_STATE.PLAYING) return; 244 | 245 | // Process all unread msgs 246 | ArrayList unreadMsgs = networkManager.getUnreadMsgs(); 247 | for (String msg: unreadMsgs) { 248 | if (gameState == GAME_STATE.PLAYING) { 249 | processMsg(msg); 250 | uiManager.addInPacket(); 251 | } else { // Game over 252 | ; // Throw packet 253 | } 254 | } 255 | } 256 | 257 | public void networkWrite() { 258 | // Game over? 259 | if (gameState != GAME_STATE.PLAYING) return; 260 | 261 | // Need sync? 262 | if (! needSync) return; 263 | 264 | Packet packet = networkManager.makeMarioStatePacket(marioMe); 265 | String packetStr = networkManager.packetToString(packet); 266 | String channel = ROOM_NUM + "CHANNEL_MARIO_" + MY_MARIO_NUM; 267 | pubSocket.sendMore(channel); 268 | pubSocket.send(packetStr); 269 | 270 | // Notify UIManager 271 | uiManager.addOutPacket(); 272 | } 273 | 274 | public void processMsg(String msg) { 275 | if (frame < 100) System.out.println("Frame " + frame + ": " + msg); 276 | 277 | Packet packet = networkManager.stringToPacket(msg); 278 | 279 | // Judge packet type 280 | switch (packet.type) { 281 | case "GAME_OVER": 282 | if (packet.whoWon == MY_MARIO_NUM) { // Win 283 | gameState = GAME_STATE.WIN; 284 | } else if (packet.whoWon == (1 - MY_MARIO_NUM)) { // Lose 285 | gameState = GAME_STATE.LOSE; 286 | } 287 | break; 288 | case "MARIO_STATE": 289 | // 마리오 상태 세팅 290 | marioRival.x = packet.x; 291 | marioRival.y = packet.y; 292 | marioRival.vx = packet.vx; 293 | marioRival.vy = packet.vy; 294 | marioRival.ax = packet.ax; 295 | marioRival.ay = packet.ay; 296 | switch (packet.motionState) { 297 | case "STANDING": marioRival.motionState = MotionState.STANDING; 298 | break; 299 | case "RUNNING": marioRival.motionState = MotionState.RUNNING; 300 | break; 301 | case "JUMPING": marioRival.motionState = MotionState.JUMPING; 302 | break; 303 | case "FALLING": marioRival.motionState = MotionState.FALLING; 304 | break; 305 | default: 306 | break; 307 | } 308 | switch (packet.faceState) { 309 | case "FACE_FRONT": marioRival.faceState = MarioFace.FACE_FRONT; 310 | break; 311 | case "FACE_LEFT": marioRival.faceState = MarioFace.FACE_LEFT; 312 | break; 313 | case "FACE_RIGHT": marioRival.faceState = MarioFace.FACE_RIGHT; 314 | break; 315 | default: 316 | break; 317 | } 318 | marioRival.boostJumping = packet.boostJumping; 319 | marioRival.jumpPressed = packet.jumpPressed; 320 | switch (packet.arrowX) { 321 | case "LEFT": marioRival.arrowX = MarioArrowX.LEFT; 322 | break; 323 | case "RIGHT": marioRival.arrowX = MarioArrowX.RIGHT; 324 | break; 325 | default: marioRival.arrowX = MarioArrowX.NONE; 326 | break; 327 | } 328 | switch (packet.arrowY) { 329 | case "UP": marioRival.arrowY = MarioArrowY.UP; 330 | break; 331 | case "DOWN": marioRival.arrowY = MarioArrowY.DOWN; 332 | break; 333 | default: marioRival.arrowY = MarioArrowY.NONE; 334 | break; 335 | } 336 | marioRival.frictionalForce = packet.frictionalForce; 337 | break; 338 | default: ; 339 | break; 340 | } 341 | } 342 | 343 | 344 | public void gameOverWinning() { 345 | gameState = GAME_STATE.WIN; 346 | 347 | // Send GAME_OVER packet to the rival 348 | Packet packet = networkManager.makeGameOverPacket(MY_MARIO_NUM); 349 | String packetStr = networkManager.packetToString(packet); 350 | String channel = ROOM_NUM + "CHANNEL_MARIO_" + MY_MARIO_NUM; 351 | pubSocket.sendMore(channel); 352 | pubSocket.send(packetStr); 353 | } 354 | 355 | public void resetGame() { 356 | gameState = GAME_STATE.PLAYING; 357 | marioMe.x = 150 + MY_MARIO_NUM * 300; 358 | marioMe.y = 0; 359 | marioMe.vx = 0; 360 | marioMe.vy = 0; 361 | marioMe.ax = 0; 362 | marioMe.ay = GRAVITY; 363 | needSync = true; 364 | msgQueue.clear(); 365 | } 366 | 367 | 368 | // ============================= RENDERING ================================= 369 | 370 | 371 | public void render() { 372 | // Draw Background 373 | drawBackground(); 374 | 375 | // Draw map 376 | drawMap(); 377 | 378 | // Draw the Marios' sprite 379 | noFill(); 380 | noStroke(); 381 | marioRival.display(); 382 | marioMe.display(); 383 | 384 | // Draw UI 385 | uiManager.display(); 386 | 387 | // Game Over? 388 | textSize(50); 389 | fill(255, 174, 201); 390 | textAlign(CENTER, CENTER); 391 | if (gameState == GAME_STATE.WIN) { 392 | String gameStateStr = "WIN"; 393 | text(gameStateStr, WIDTH / 2, HEIGHT / 2); 394 | } else if (gameState == GAME_STATE.LOSE) { 395 | String gameStateStr = "LOSE"; 396 | text(gameStateStr, WIDTH / 2, HEIGHT / 2); 397 | } 398 | } 399 | 400 | public void drawBackground() { 401 | fill(107, 140, 255); 402 | noStroke(); 403 | rect(0, 0, WIDTH, HEIGHT); 404 | bgImgSprite.display(-1, 8); 405 | princessSprite.display(6639, 384 - princessSprite.getHeight()); 406 | } 407 | 408 | public void drawMap() { 409 | blockManager.display(); 410 | } 411 | 412 | 413 | 414 | 415 | 416 | enum MotionState { 417 | STANDING, RUNNING, JUMPING, FALLING, 418 | } 419 | 420 | enum MarioFace { 421 | FACE_LEFT, FACE_RIGHT, FACE_FRONT 422 | } 423 | 424 | enum MarioArrowX { 425 | NONE, 426 | LEFT, 427 | RIGHT 428 | } 429 | 430 | enum MarioArrowY { 431 | NONE, 432 | UP, 433 | DOWN 434 | } 435 | 436 | class Mario { 437 | // Space state 438 | int width, height; 439 | float x = 150, y = 0; 440 | float vx = 0; 441 | float vy = 0; 442 | float ax = 0; 443 | float ay = GRAVITY; 444 | 445 | // Force state 446 | float frictionalForce = 0; 447 | 448 | // Motion state 449 | MotionState motionState = MotionState.JUMPING; 450 | MarioFace faceState = MarioFace.FACE_RIGHT; 451 | 452 | // Keyboard State 453 | MarioArrowX arrowX = MarioArrowX.NONE; 454 | MarioArrowY arrowY = MarioArrowY.NONE; 455 | boolean jumpPressed = false; 456 | 457 | boolean boostJumping = false; 458 | 459 | // Sprite state 460 | Sprite currentSprite = null; 461 | 462 | // Sprites 463 | Sprite marioStanding, marioRunning, marioJumping, marioFalling; 464 | 465 | Camera camera; 466 | 467 | boolean isGhost = false; 468 | 469 | Mario(boolean blackWhite) { 470 | // Load sprites 471 | if (! blackWhite) { 472 | marioStanding = new Sprite("img/mario_standing_", 1, true); 473 | marioRunning = new Sprite("img/mario_running_", 4, true); 474 | marioJumping = new Sprite("img/mario_jumping_", 1, true); 475 | marioFalling = new Sprite("img/mario_falling_", 2, true); 476 | } else { 477 | marioStanding = new Sprite("img/bw_mario_standing_", 1, true); 478 | marioRunning = new Sprite("img/bw_mario_running_", 4, true); 479 | marioJumping = new Sprite("img/bw_mario_jumping_", 1, true); 480 | marioFalling = new Sprite("img/bw_mario_falling_", 2, true); 481 | 482 | isGhost = true; 483 | } 484 | 485 | width = marioStanding.getWidth(); 486 | height = marioStanding.getHeight(); 487 | 488 | frictionalForce = MY_FRICTIONAL_FORCE; 489 | 490 | // Make an own camera 491 | camera = new Camera(); 492 | } 493 | 494 | public void update() { 495 | _updateMotion(); 496 | _updateSpeed(); 497 | _updatePosition(); 498 | } 499 | 500 | public void display() { 501 | // Select sprite 502 | if (motionState == MotionState.FALLING) currentSprite = marioFalling; 503 | else if (motionState == MotionState.JUMPING) currentSprite = marioJumping; 504 | else if (motionState == MotionState.RUNNING) currentSprite = marioRunning; 505 | else if (motionState == MotionState.STANDING) currentSprite = marioStanding; 506 | 507 | // Select face direction 508 | if (faceState == MarioFace.FACE_LEFT) { 509 | currentSprite.display(x, y, true); 510 | } else 511 | currentSprite.display(x, y); 512 | } 513 | 514 | public float getCenterX() { 515 | return x + 0.5f * width; 516 | } 517 | 518 | // 히트박스 위의 두 정점 519 | public Coordinate[] getHitboxTop() { 520 | Coordinate vertx1 = new Coordinate(x + 2, y); 521 | Coordinate vertx2 = new Coordinate(x + width - 2, y); 522 | return new Coordinate[]{vertx1, vertx2}; 523 | } 524 | 525 | // 히트박스 아래의 두 정점 526 | public Coordinate[] getHitboxBottom() { 527 | Coordinate vertx1 = new Coordinate(x + 2, y + height); 528 | Coordinate vertx2 = new Coordinate(x + width - 2, y + height); 529 | return new Coordinate[]{vertx1, vertx2}; 530 | } 531 | 532 | public Hitbox getHitboxHead() { 533 | Hitbox hitbox = new Hitbox(); 534 | hitbox.topLeft = new Coordinate(x + 2, y); 535 | hitbox.topRight = new Coordinate(x + width - 2, y); 536 | hitbox.bottomLeft = new Coordinate(x + 2, y + height - 25); 537 | hitbox.bottomRight = new Coordinate(x + width - 2, y + height - 25); 538 | return hitbox; 539 | } 540 | 541 | public Hitbox getHitbox() { 542 | Hitbox hitbox = new Hitbox(); 543 | hitbox.topLeft = new Coordinate(x + 2, y); 544 | hitbox.topRight = new Coordinate(x + width - 2, y); 545 | hitbox.bottomLeft = new Coordinate(x + 2, y + height); 546 | hitbox.bottomRight = new Coordinate(x + width - 2, y + height); 547 | return hitbox; 548 | } 549 | 550 | public void onKeyPress(char key) { 551 | if (key == 'c' || key == 'C') { 552 | pendingKeyboardEvents.add("_jump"); 553 | } else if (key == 'x' || key == '#') { 554 | ; 555 | } else if (key == ENTER) { 556 | if (gameState != GAME_STATE.PLAYING) resetGame(); 557 | }else if (key == CODED) { 558 | if (keyCode == UP) { 559 | pendingKeyboardEvents.add("_up"); 560 | } else if (keyCode == DOWN) { 561 | pendingKeyboardEvents.add("_down"); 562 | } 563 | if (keyCode == LEFT) { 564 | pendingKeyboardEvents.add("_left"); 565 | } else if (keyCode == RIGHT) { 566 | pendingKeyboardEvents.add("_right"); 567 | } 568 | } 569 | } 570 | 571 | public void onKeyRelease(char key) { 572 | if (key == 'c' || key == 'C') { 573 | pendingKeyboardEvents.add("_release_jump"); 574 | } else if (key == 'x' || key == '#') { 575 | ; 576 | } else if (key == CODED) { 577 | if (keyCode == UP) { 578 | pendingKeyboardEvents.add("_release_up"); 579 | } else if (keyCode == DOWN) { 580 | pendingKeyboardEvents.add("_release_down"); 581 | } 582 | if (keyCode == LEFT) { 583 | pendingKeyboardEvents.add("_release_left"); 584 | } else if (keyCode == RIGHT) { 585 | pendingKeyboardEvents.add("_release_right"); 586 | } 587 | } 588 | } 589 | 590 | public void processKeyboard() { 591 | if (pendingKeyboardEvents.isEmpty()) needSync = false; 592 | else needSync = true; 593 | 594 | for (String event: pendingKeyboardEvents) { 595 | switch (event) { 596 | case "_jump": _jump(); 597 | break; 598 | case "_left": _left(); 599 | break; 600 | case "_right": _right(); 601 | break; 602 | case "_up": arrowY = MarioArrowY.UP; 603 | break; 604 | case "_down": arrowY = MarioArrowY.DOWN; 605 | break; 606 | case "_release_jump": jumpPressed = false; 607 | break; 608 | case "_release_left": if (arrowX == MarioArrowX.LEFT) arrowX = MarioArrowX.NONE;; 609 | break; 610 | case "_release_right": if (arrowX == MarioArrowX.RIGHT) arrowX = MarioArrowX.NONE; 611 | break; 612 | case "_release_up": if (arrowY == MarioArrowY.UP) arrowY = MarioArrowY.NONE; 613 | break; 614 | case "_release_down": if (arrowY == MarioArrowY.DOWN) arrowY = MarioArrowY.NONE; 615 | break; 616 | default: ; 617 | break; 618 | } 619 | } 620 | pendingKeyboardEvents.clear(); 621 | } 622 | 623 | public void _jump() { 624 | if (motionState != MotionState.JUMPING) { 625 | motionState = MotionState.JUMPING; 626 | vy = -MARIO_JUMP_FORCE; // 위로 튕겨준다 627 | } 628 | jumpPressed = true; 629 | } 630 | 631 | public void _boostJump() { 632 | if (! boostJumping) { 633 | boostJumping = true; 634 | vy -= BOOST_JUMP_FORCE; 635 | } 636 | } 637 | 638 | public void _disJump() { 639 | // 점핑 상태 해제 640 | if (motionState == MotionState.JUMPING) motionState = MotionState.STANDING; 641 | boostJumping = false; 642 | vy = 0; 643 | } 644 | 645 | public void _friction() { 646 | // 마찰하여 감속 647 | if (vx > 0 && motionState != MotionState.JUMPING) ax = -frictionalForce; 648 | else if (vx < 0 && motionState != MotionState.JUMPING) ax = frictionalForce; 649 | } 650 | 651 | public void _left() { 652 | arrowX = MarioArrowX.LEFT; 653 | 654 | // Turn the face to left 655 | faceState = MarioFace.FACE_LEFT; 656 | // 서있던거라면 달려라 657 | if (motionState == MotionState.STANDING) motionState = MotionState.RUNNING; 658 | 659 | // Give a force to left 660 | if (vx > 0) vx = 0; 661 | ax = -MARIO_FORCE; 662 | } 663 | 664 | public void _right() { 665 | arrowX = MarioArrowX.RIGHT; 666 | 667 | // Turn the face to right 668 | faceState = MarioFace.FACE_RIGHT; 669 | // 서있던거라면 달려라 670 | if (motionState == MotionState.STANDING) motionState = MotionState.RUNNING; 671 | 672 | // Give a force to right 673 | if (vx < 0) vx = 0; 674 | ax = MARIO_FORCE; 675 | } 676 | 677 | public void _updateMotion() { 678 | if (arrowX == MarioArrowX.LEFT) { 679 | faceState = MarioFace.FACE_LEFT; 680 | 681 | if (motionState == MotionState.STANDING) { // 달리기 로직 682 | motionState = MotionState.RUNNING; 683 | } 684 | } else if (arrowX == MarioArrowX.RIGHT) { 685 | faceState = MarioFace.FACE_RIGHT; 686 | if (motionState == MotionState.STANDING) { // 달리기 로직 687 | motionState = MotionState.RUNNING; 688 | } 689 | } else if (arrowX == MarioArrowX.NONE) { 690 | // 멈추기 로직 691 | if (motionState == MotionState.RUNNING) motionState = MotionState.STANDING; 692 | _friction(); 693 | } 694 | } 695 | 696 | public void _updateSpeed() { 697 | // Y-axis 698 | vy += ay; 699 | // 부스트 점프 700 | if (vy < 0 && vy > -4 && jumpPressed && ! boostJumping) _boostJump(); 701 | 702 | // X-axis 703 | float previousVx = vx; 704 | vx += ax; 705 | // Limit speed 706 | if (Math.abs(vx) > MARIO_VX_LIMIT) { 707 | if (vx > 0) { 708 | vx = MARIO_VX_LIMIT; 709 | } else { 710 | vx = -MARIO_VX_LIMIT; 711 | } 712 | } else if (Math.abs(vx) < MARIO_FORCE) { 713 | vx = 0; 714 | ax = 0; 715 | } else if (previousVx * vx < 0) { // Symbol changed 716 | vx = 0; 717 | ax = 0; 718 | } 719 | } 720 | 721 | public void _updatePosition() { 722 | // Game Over? 723 | if (gameState != GAME_STATE.PLAYING) return; 724 | 725 | // Update X 726 | float previousX = x; 727 | x += vx; 728 | if (x < 0) x = 0; 729 | if (x > 7000) x = 7000; 730 | 731 | // Collision detection: X 732 | if (collisionDetector.left(this) || collisionDetector.right(this)){ 733 | x = previousX; 734 | vx = 0; 735 | } 736 | 737 | // Update Y 738 | float previousY = y; 739 | y += vy; 740 | 741 | // Collision detection: Y 742 | if (collisionDetector.up(this)) { 743 | y = previousY; 744 | vy = 0; 745 | } 746 | if (y > HEIGHT - mario0.height || collisionDetector.down(this) ) { 747 | y = previousY; 748 | vy = 0; 749 | _disJump(); 750 | } 751 | 752 | // Enemy Collision Detection 753 | if (! isGhost) { // FTS: Favor The Shooter, 고스트는 밟기 판정을 안함 754 | if (enemyCD.isTrample(getHitboxBottom(), marioRival.getHitboxHead(), this) 755 | == HitType.ENEMY) { 756 | gameOverWinning(); 757 | } 758 | } 759 | 760 | // Camera following 761 | if (getCenterX() - camera.getCenterX() > 0) { // 마리오 너무 오른 쪽 762 | camera.x = getCenterX() - 0.5f * camera.width; 763 | } else if (camera.getCenterX() - getCenterX() > CAMERA_RANGE_LEFT) { // 마리오 너무 왼쪽 764 | camera.x = getCenterX() + CAMERA_RANGE_LEFT - 0.5f * camera.width; 765 | if (camera.x < 0) camera.x = 0; 766 | } 767 | } 768 | } 769 | 770 | 771 | 772 | int framesPerShot = FRAMES_PER_SHOT; 773 | int spriteScale = ASSET_SCALE; 774 | 775 | /** 776 | * Class for animating a sequence of GIFs 777 | */ 778 | class Sprite { 779 | PImage[] images; 780 | PImage[] mirroredImages; 781 | int imageCount; 782 | int frame; 783 | 784 | Sprite(String imagePrefix, int count) { 785 | imageCount = count; 786 | images = new PImage[imageCount]; 787 | 788 | for (int i = 0; i < imageCount; ++i) { 789 | // Use nf() to number format 'i' into four digits 790 | String filename = imagePrefix + nf(i, 4) + ".gif"; 791 | PImage img = loadImage(filename); 792 | images[i] = _enlarge(img, 2); 793 | } 794 | } 795 | 796 | Sprite(String imagePrefix, int count, boolean hasMirrored) { 797 | 798 | imageCount = count; 799 | images = new PImage[imageCount]; 800 | 801 | for (int i = 0; i < imageCount; ++i) { 802 | // Use nf() to number format 'i' into four digits 803 | String filename = imagePrefix + nf(i, 4) + ".gif"; 804 | PImage img = loadImage(filename); 805 | images[i] = _enlarge(img, 2); 806 | } 807 | 808 | if (hasMirrored) { 809 | mirroredImages = new PImage[imageCount]; 810 | for (int i = 0; i < imageCount; ++i) { 811 | mirroredImages[i] = _flip(images[i]); 812 | } 813 | } 814 | } 815 | 816 | public void display(float xpos, float ypos) { // 월드 좌표 받고 817 | frame = (frame + 1) % (imageCount * framesPerShot); 818 | int shotIndex = frame / framesPerShot; 819 | image(images[shotIndex], xpos - marioMe.camera.x, ypos, images[shotIndex].width, 820 | images[shotIndex].height); // 카메라 좌표계로 그려줌 821 | } 822 | 823 | public void display(float xpos, float ypos, boolean reverse) { 824 | if (! reverse) display(xpos - marioMe.camera.x, ypos); 825 | 826 | frame = (frame + 1) % (imageCount * framesPerShot); 827 | int shotIndex = frame / framesPerShot; 828 | image(mirroredImages[shotIndex], xpos - marioMe.camera.x, ypos, mirroredImages[shotIndex].width, 829 | mirroredImages[shotIndex].height); 830 | } 831 | 832 | public int getWidth() { 833 | return images[0].width; 834 | } 835 | 836 | public int getHeight() { 837 | return images[0].height; 838 | } 839 | 840 | public PImage _enlarge(PImage image, int multiple) { 841 | PImage newImg; 842 | newImg = createImage(image.width * multiple, image.height * multiple, ARGB); 843 | for (int i = 0; i < image.width; ++i) { 844 | for (int j = 0; j < image.height; ++j) { 845 | newImg.set(2 * i, 2 * j, image.get(i, j)); 846 | newImg.set(2 * i, 2 * j + 1, image.get(i, j)); 847 | newImg.set(2 * i + 1, 2 * j, image.get(i, j)); 848 | newImg.set(2 * i + 1, 2 * j + 1, image.get(i, j)); 849 | } 850 | } 851 | 852 | return newImg; 853 | } 854 | 855 | public PImage _flip(PImage image) { 856 | PImage reverse; 857 | reverse = createImage(image.width, image.height, ARGB); 858 | 859 | for (int i = 0; i < image.width; ++i) { 860 | for (int j = 0; j < image.height; ++j) { 861 | int xPixel, yPixel; 862 | xPixel = image.width - 1 - i; 863 | yPixel = j; 864 | reverse.pixels[yPixel * image.width + xPixel] = image.pixels[j * image.width + i]; 865 | } 866 | } 867 | return reverse; 868 | } 869 | 870 | } 871 | 872 | class SharedSprite extends Sprite { 873 | 874 | public SharedSprite(String imagePrefix, int count) { 875 | super(imagePrefix, count); 876 | } 877 | 878 | public SharedSprite(String imagePrefix, int count, boolean hasMirrored) { 879 | super(imagePrefix, count, hasMirrored); 880 | } 881 | 882 | public void display(float xpos, float ypos) { 883 | int shotIndex = frame / framesPerShot; 884 | image(images[shotIndex], xpos - marioMe.camera.x, ypos, 885 | images[shotIndex].width, images[shotIndex].height); 886 | } 887 | 888 | public void display(float xpos, float ypos, boolean reverse) { 889 | int shotIndex = frame / framesPerShot; 890 | 891 | if (! reverse) { 892 | image(images[shotIndex], xpos - marioMe.camera.x, ypos, 893 | images[shotIndex].width, images[shotIndex].height); 894 | } else { 895 | image(mirroredImages[shotIndex], xpos - marioMe.camera.x, ypos, 896 | mirroredImages[shotIndex].width, mirroredImages[shotIndex].height); 897 | } 898 | } 899 | 900 | public void update() { 901 | frame = (frame + 1) % (imageCount * framesPerShot); 902 | } 903 | } 904 | 905 | class TextSprite { 906 | private float x, y; 907 | private float vy; 908 | private String _text; 909 | private int size; 910 | private int frame; 911 | 912 | public TextSprite(String text, int size, float x, float y, float vy) { 913 | this.x = x; 914 | this.y = y; 915 | this._text = text; 916 | this.size = size; 917 | this.vy = vy; 918 | } 919 | 920 | public void update() { 921 | this.y += vy; 922 | ++frame; 923 | } 924 | 925 | public void display() { 926 | textSize(size); 927 | fill(255, 255, 255); 928 | textAlign(CENTER, CENTER); 929 | text(_text, x, y); 930 | } 931 | } 932 | 933 | 934 | 935 | 936 | 937 | class Camera { 938 | int width, height; // Camera sight 939 | float x = 0, y = 0; // Camera's absolute coordinate 940 | 941 | Camera() { 942 | width = WIDTH; 943 | height = HEIGHT; 944 | } 945 | 946 | float getCenterX() { 947 | return x + 0.5f * width; 948 | } 949 | } 950 | 951 | 952 | 953 | 954 | 955 | class Block { 956 | int x = 0, y = 0; 957 | int xB = 0, yB = 0; 958 | char blockType; 959 | boolean visible = true; 960 | SharedSprite sprite; 961 | 962 | public Block(int xB, int yB, char blockType) { 963 | this.xB = xB; 964 | this.yB = yB; 965 | this.x = 16 * ASSET_SCALE * xB + BLOCK_OFFSET; 966 | this.y = 16 * ASSET_SCALE * yB; 967 | 968 | this.visible = true; 969 | this.blockType = blockType; 970 | } 971 | 972 | public void display() { 973 | if (sprite != null) { 974 | sprite.display(x, y); // 월드 좌표 받음 975 | } 976 | } 977 | } 978 | 979 | class Coordinate { 980 | float x = 0, y = 0; 981 | 982 | public Coordinate(float x, float y) { 983 | this.x = x; 984 | this.y = y; 985 | } 986 | } 987 | 988 | class BlockManager { 989 | SharedSprite spriteGround; 990 | SharedSprite spriteBrick; 991 | SharedSprite spriteBlock; 992 | SharedSprite spriteQuestion; 993 | SharedSprite spriteGold; 994 | ArrayList> blocks = new ArrayList>(); 995 | 996 | public BlockManager() { 997 | // Load sprites 998 | spriteGround = new SharedSprite("img/ground_", 1); 999 | spriteBlock = new SharedSprite("img/block_", 1); 1000 | spriteBrick = new SharedSprite("img/brick_", 1); 1001 | spriteQuestion = new SharedSprite("img/question_", 1); 1002 | spriteGold = new SharedSprite("img/gold_", 4); 1003 | 1004 | // Read map.txt 1005 | String[] lines = loadStrings("map.txt"); 1006 | // Make blocks 1007 | println("Start to make blocks.."); 1008 | for (int i = 0; i < lines.length; ++i) { 1009 | String line = lines[i]; 1010 | char[] charArray = line.toCharArray(); 1011 | ArrayList blockRow = new ArrayList(); 1012 | for (int j = 0; j < charArray.length; ++j) { 1013 | char c = charArray[j]; 1014 | Block block = new Block(j, i, c); 1015 | switch (c) { 1016 | case ' ' : 1017 | break; 1018 | case '#' : ; block.sprite = spriteGround; // Ground 1019 | break; 1020 | case 'B' : block.sprite = spriteBlock; // Block 1021 | break; 1022 | case 'R' : block.sprite = spriteBrick; // Block 1023 | break; 1024 | case '?' : block.sprite = spriteQuestion; // Question mark 1025 | break; 1026 | case 'O' : block.sprite = spriteGold; // Gold 1027 | break; 1028 | default : 1029 | break; 1030 | } 1031 | 1032 | blockRow.add(block); 1033 | print(c); 1034 | } 1035 | blocks.add(blockRow); 1036 | println(); 1037 | } 1038 | } 1039 | 1040 | public void display() { 1041 | int xStart; 1042 | if ((x2bX(marioMe.camera.getCenterX()) - 11) < 0) xStart = 0; 1043 | else xStart = x2bX(marioMe.camera.getCenterX()) - 11; 1044 | int xEnd = xStart + 23; 1045 | 1046 | for (int i = 0; i < blocks.size(); ++i) { 1047 | for (int j = xStart; j < Math.min(xEnd + 1, blocks.get(i).size()); ++j) { 1048 | Block block = blocks.get(i).get(j); 1049 | block.display(); 1050 | } 1051 | } 1052 | } 1053 | 1054 | public Coordinate block2Coor(int bX, int bY) { 1055 | Coordinate coordinate = new Coordinate(16 * ASSET_SCALE * bX, 1056 | 16 * ASSET_SCALE * bY); 1057 | return coordinate; 1058 | } 1059 | 1060 | public int y2bY(float y) { 1061 | return (int) y / (16 * ASSET_SCALE); 1062 | } 1063 | 1064 | public int x2bX(float x) { 1065 | return (int) (x - BLOCK_OFFSET) / (16 * ASSET_SCALE); 1066 | } 1067 | } 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | class CollisionDetector { 1074 | public boolean up(Mario mario) { 1075 | if ((int)mario.y <= 0) return false; 1076 | 1077 | int step = 24; 1078 | for (int i = 0; i < 2; ++i) { 1079 | float x = mario.x + i * step; 1080 | int bX = blockManager.x2bX(x); 1081 | int bY = blockManager.y2bY(mario.y); 1082 | 1083 | ArrayList blockRow = blockManager.blocks.get(bY); 1084 | if (blockRow.size() <= bX) continue; // 블록이 없네 1085 | 1086 | Block block = blockRow.get(bX); 1087 | if (block == null) continue; 1088 | if (block.blockType == ' ' || block.blockType == 'O') 1089 | continue; 1090 | else return true; 1091 | } 1092 | 1093 | // 위에서 true 로 미리 빠져나가지 않았다 1094 | return false; 1095 | } 1096 | 1097 | public boolean down(Mario mario) { 1098 | if ((int)mario.y <= 0) return false; 1099 | 1100 | int step = 24; 1101 | for (int i = 0; i < 2; ++i) { 1102 | float x = mario.x + i * step; 1103 | int bX = blockManager.x2bX(x); 1104 | int bY = blockManager.y2bY(mario.y + mario.height); 1105 | 1106 | ArrayList blockRow = blockManager.blocks.get(bY); 1107 | if (blockRow.size() <= bX) continue; // 블록이 없네 1108 | 1109 | Block block = blockRow.get(bX); 1110 | if (block == null) continue; 1111 | if (block.blockType == ' ' || block.blockType == 'O') continue; 1112 | else return true; 1113 | } 1114 | 1115 | // 위에서 true 로 미리 빠져나가지 않았다 1116 | return false; 1117 | } 1118 | 1119 | public boolean left(Mario mario) { 1120 | if ((int)mario.y <= 0) return false; 1121 | 1122 | int step = mario.height / 2; 1123 | for (int i = 0; i < 3; ++i) { 1124 | float y = mario.y + i * step; 1125 | int bX = blockManager.x2bX(mario.x); 1126 | int bY = blockManager.y2bY(y); 1127 | 1128 | ArrayList blockRow = blockManager.blocks.get(bY); 1129 | if (blockRow.size() <= bX) continue; // 블록이 없네 1130 | 1131 | Block block = blockRow.get(bX); 1132 | if (block == null) continue; 1133 | if (block.blockType == ' ' || block.blockType == 'O') continue; 1134 | else return true; 1135 | } 1136 | 1137 | // 위에서 true 로 미리 빠져나가지 않았다 1138 | return false; 1139 | } 1140 | 1141 | public boolean right(Mario mario) { 1142 | if ((int)mario.y <= 0) return false; 1143 | 1144 | int step = mario.height / 2; 1145 | for (int i = 0; i < 3; ++i) { 1146 | float y = mario.y + i * step; 1147 | int bX = blockManager.x2bX(mario.x + mario.width); 1148 | int bY = blockManager.y2bY(y); 1149 | 1150 | ArrayList blockRow = blockManager.blocks.get(bY); 1151 | if (blockRow.size() <= bX) continue; // 블록이 없네 1152 | 1153 | Block block = blockRow.get(bX); 1154 | if (block == null) continue; 1155 | if (block.blockType == ' ' || block.blockType == 'O') continue; 1156 | else return true; 1157 | } 1158 | 1159 | // 위에서 true 로 미리 짜져나가지 않았다 1160 | return false; 1161 | } 1162 | } 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | class Rect { 1169 | Coordinate topLeft; 1170 | Coordinate topRight; 1171 | Coordinate bottomLeft; 1172 | Coordinate bottomRight; 1173 | } 1174 | 1175 | class Hitbox extends Rect {} 1176 | 1177 | enum HitType { 1178 | NONE, 1179 | ENEMY 1180 | } 1181 | 1182 | class EnemyCollisionDetector { 1183 | public HitType isTrample(Coordinate[] myHitboxBottom, Hitbox enemyHitbox, 1184 | Mario marioMe) { 1185 | if (marioMe.vy < 0.1) return HitType.NONE; 1186 | 1187 | Coordinate[] hbVertexes = new Coordinate[]{ 1188 | enemyHitbox.topLeft, 1189 | enemyHitbox.topRight, 1190 | enemyHitbox.bottomLeft, 1191 | enemyHitbox.bottomRight 1192 | }; 1193 | for (Coordinate foot: myHitboxBottom) { 1194 | boolean isMinX = true; 1195 | for (Coordinate vertx: hbVertexes) { 1196 | if (foot.x >= vertx.x) { 1197 | isMinX = false; 1198 | break; 1199 | } 1200 | } 1201 | if (isMinX) continue; 1202 | 1203 | boolean isMaxX = true; 1204 | for (Coordinate vertx: hbVertexes) { 1205 | if (foot.x <= vertx.x) { 1206 | isMaxX = false; 1207 | break; 1208 | } 1209 | } 1210 | if (isMaxX) continue; 1211 | 1212 | boolean isMinY = true; 1213 | for (Coordinate vertx: hbVertexes) { 1214 | if (foot.y >= vertx.y) { 1215 | isMinY = false; 1216 | break; 1217 | } 1218 | } 1219 | if (isMinY) continue; 1220 | 1221 | boolean isMaxY = true; 1222 | for (Coordinate vertx: hbVertexes) { 1223 | if (foot.y <= vertx.y) { 1224 | isMaxY = false; 1225 | break; 1226 | } 1227 | } 1228 | if (isMaxY) continue; 1229 | 1230 | // Neither min nor max, return HIT 1231 | return HitType.ENEMY; 1232 | } 1233 | 1234 | // Not hit 1235 | return HitType.NONE; 1236 | } 1237 | } 1238 | 1239 | 1240 | 1241 | 1242 | 1243 | class Subscriber implements Runnable { 1244 | Context context; 1245 | Socket subSocket; 1246 | 1247 | // Constructor 1248 | public Subscriber () { 1249 | context = ZMQ.context(1); 1250 | subSocket = context.socket(ZMQ.SUB); 1251 | subSocket.connect("tcp://" + SERVER_HOST + ":" + XPUB_PORT); 1252 | String channel = (ROOM_NUM + "CHANNEL_MARIO_" + String.valueOf(1 - MY_MARIO_NUM)); 1253 | // String channel = (ROOM_NUM + "CHANNEL_MARIO_0"); 1254 | 1255 | subSocket.subscribe(channel.getBytes(ZMQ.CHARSET)); 1256 | } 1257 | 1258 | public void run() { 1259 | while (!Thread.currentThread().isInterrupted()) { 1260 | /* 동기적으로 읽기를 해서, sleep 안해줘도 된다. 샘플코드에서 따온 코드임 1261 | */ 1262 | 1263 | // Read envelope with address 1264 | String channel = subSocket.recvStr(); 1265 | // Read message contents 1266 | String contents = subSocket.recvStr(); 1267 | //System.out.println("Received from: " + channel + " Contents: " + contents); 1268 | msgQueue.add(contents); 1269 | } 1270 | } 1271 | 1272 | public void finallize() throws Throwable { 1273 | try{ 1274 | if (subSocket != null) subSocket.close(); 1275 | if (context != null) context.term(); 1276 | } 1277 | finally { 1278 | super.finalize(); 1279 | } 1280 | } 1281 | } 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | class Packet { 1288 | public String type; // "MARIO_STATE" / "GAME_OVER" 1289 | public int whoWon; // 0, 1, -1 1290 | public float x; 1291 | public float y; 1292 | public float vx; 1293 | public float vy; 1294 | public float ax; 1295 | public float ay; 1296 | public String motionState; 1297 | public String faceState; 1298 | public boolean boostJumping; 1299 | public boolean jumpPressed; 1300 | public String arrowX; 1301 | public String arrowY; 1302 | public float frictionalForce; 1303 | } 1304 | 1305 | 1306 | 1307 | 1308 | 1309 | class NetworkManager { 1310 | public ArrayList getUnreadMsgs() { 1311 | ArrayList unreadMsgs = new ArrayList(); 1312 | String msg; 1313 | while ((msg = msgQueue.poll()) != null) { 1314 | unreadMsgs.add(msg); 1315 | } 1316 | return unreadMsgs; 1317 | } 1318 | 1319 | public Packet makeMarioStatePacket(Mario mario) { 1320 | Packet packet = new Packet(); 1321 | packet.type = "MARIO_STATE"; 1322 | packet.whoWon = -1; // For none 1323 | packet.x = mario.x; 1324 | packet.y = mario.y; 1325 | packet.vx = mario.vx; 1326 | packet.vy = mario.vy; 1327 | packet.ax = mario.ax; 1328 | packet.ay = mario.ay; 1329 | String motionState = ""; 1330 | switch (mario.motionState) { 1331 | case STANDING: motionState = "STANDING"; 1332 | break; 1333 | case RUNNING: motionState = "RUNNING"; 1334 | break; 1335 | case JUMPING: motionState = "JUMPING"; 1336 | break; 1337 | case FALLING: motionState = "FALLING"; 1338 | break; 1339 | default: ; 1340 | break; 1341 | } 1342 | packet.motionState = motionState; 1343 | String faceState = ""; 1344 | switch (mario.faceState) { 1345 | case FACE_FRONT: faceState = "FACE_FRONT"; 1346 | break; 1347 | case FACE_LEFT: faceState = "FACE_LEFT"; 1348 | break; 1349 | case FACE_RIGHT: faceState = "FACE_RIGHT"; 1350 | break; 1351 | default: ; 1352 | break; 1353 | } 1354 | packet.faceState = faceState; 1355 | packet.boostJumping = mario.boostJumping; 1356 | packet.jumpPressed = mario.jumpPressed; 1357 | switch (mario.arrowX) { 1358 | case LEFT: packet.arrowX = "LEFT"; 1359 | break; 1360 | case RIGHT: packet.arrowX = "RIGHT"; 1361 | break; 1362 | default: packet.arrowX = "NONE"; 1363 | break; 1364 | } 1365 | switch (mario.arrowY) { 1366 | case UP: packet.arrowY = "UP"; 1367 | break; 1368 | case DOWN: packet.arrowY = "DOWN"; 1369 | break; 1370 | default: packet.arrowY = "NONE"; 1371 | break; 1372 | } 1373 | packet.frictionalForce = mario.frictionalForce; 1374 | 1375 | return packet; 1376 | } 1377 | 1378 | public Packet makeGameOverPacket(int whoWon) { 1379 | Packet packet = new Packet(); 1380 | packet.type = "GAME_OVER"; 1381 | packet.whoWon = whoWon; 1382 | return packet; 1383 | } 1384 | 1385 | public String packetToString(Packet packet) { 1386 | return gson.toJson(packet); 1387 | } 1388 | 1389 | public Packet stringToPacket(String packetStr) { 1390 | return gson.fromJson(packetStr, Packet.class); 1391 | } 1392 | } 1393 | 1394 | 1395 | 1396 | 1397 | 1398 | class UIManger { 1399 | public LinkedList inPackets; 1400 | public LinkedList outPackets; 1401 | 1402 | public UIManger() { 1403 | inPackets = new LinkedList(); 1404 | outPackets = new LinkedList(); 1405 | } 1406 | 1407 | public void update() { 1408 | // inPackets 1409 | for (int i = 0; i < inPackets.size(); ++i) { 1410 | inPackets.get(i).update(); 1411 | } 1412 | // GC 1413 | while (true) { 1414 | if (inPackets.isEmpty()) break; 1415 | 1416 | TextSprite textSprite = inPackets.getFirst(); 1417 | if (textSprite.y > HEIGHT) { 1418 | inPackets.removeFirst(); 1419 | } else break; 1420 | } 1421 | 1422 | // outPackets 1423 | for (int i = 0; i < outPackets.size(); ++i) { 1424 | outPackets.get(i).update(); 1425 | } 1426 | // GC 1427 | while (true) { 1428 | if (outPackets.isEmpty()) break; 1429 | 1430 | TextSprite textSprite = outPackets.getFirst(); 1431 | if (textSprite.y < 0) { 1432 | outPackets.removeFirst(); 1433 | } else break; 1434 | } 1435 | } 1436 | 1437 | public void display() { 1438 | if (! SHOW_PACKET_INDICATOR) return; 1439 | 1440 | // inPackets 1441 | for (int i = 0; i < inPackets.size(); ++i) { 1442 | inPackets.get(i).display(); 1443 | } 1444 | 1445 | // outPackets 1446 | for (int i = 0; i < outPackets.size(); ++i) { 1447 | outPackets.get(i).display(); 1448 | } 1449 | } 1450 | 1451 | public void addInPacket() { 1452 | inPackets.addLast(new TextSprite("@", 30, WIDTH, 0, 20));; 1453 | } 1454 | 1455 | public void addOutPacket() { 1456 | outPackets.addLast(new TextSprite("@", 30, 0, HEIGHT, -20));; 1457 | } 1458 | } -------------------------------------------------------------------------------- /pde/MarioMultiplay/code/gson-2.8.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/code/gson-2.8.0.jar -------------------------------------------------------------------------------- /pde/MarioMultiplay/code/jeromq-0.3.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/code/jeromq-0.3.5.jar -------------------------------------------------------------------------------- /pde/MarioMultiplay/code/snakeyaml-1.17.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/code/snakeyaml-1.17.jar -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/Supermario_BGM_Overworld.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/Supermario_BGM_Overworld.mp3 -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/config.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/config.yml -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/background_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/background_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/block_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/block_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/brick_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/brick_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/bw_mario_falling_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_falling_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/bw_mario_falling_0001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_falling_0001.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/bw_mario_jumping_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_jumping_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/bw_mario_running_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_running_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/bw_mario_running_0001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_running_0001.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/bw_mario_running_0002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_running_0002.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/bw_mario_running_0003.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_running_0003.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/bw_mario_standing_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/bw_mario_standing_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/gold_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/gold_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/gold_0001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/gold_0001.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/gold_0002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/gold_0002.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/gold_0003.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/gold_0003.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/ground_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/ground_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/ground_filler_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/ground_filler_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/mario_falling_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_falling_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/mario_falling_0001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_falling_0001.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/mario_jumping_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_jumping_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/mario_running_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_running_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/mario_running_0001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_running_0001.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/mario_running_0002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_running_0002.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/mario_running_0003.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_running_0003.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/mario_standing_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/mario_standing_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/princess_standing_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/princess_standing_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/img/question_0000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolspeed/MarioMultiplayer/5ca3f0383aa0d18a3f04e9b019356b8a2b84db11/pde/MarioMultiplay/data/img/question_0000.gif -------------------------------------------------------------------------------- /pde/MarioMultiplay/data/map.txt: -------------------------------------------------------------------------------- 1 | BBBBBBBBBBBBBBBBBBBBBB 2 | BBBBBBBBBBBBBBBBBBBBBB 3 | BBBBBB BBBBBBBBBBBBBBBBBBBBBB 4 | BBBBBBBBBBBBBBBBBBBBBB 5 | RRRRR RRRRRR BBBBBBBBBBBBBBBBBBBBBB 6 | BBBBBBBBBBBBBBBBBBBBBB 7 | BBBBBB BBBBBBBBBBBBBBBBBBBBBB 8 | BBBBBBBBBBBBBBBBBBBBBB 9 | RRRR RRRRR OO RRR BBBBBBBBBBBBBBBBBBBBBB 10 | O O RRRBBBBBBBBBBBBBBBBBBBBBB 11 | BBBBBBBBBBBBBBBBBBBBBB 12 | B BBBBBBBBBBBBBBBBBBBBBB 13 | #####################################################################BBBBBBBBBBBBBBBBBBBBB 14 | #####################################################################BBBBBBBBBBBBBBBBBBBBB 15 | -------------------------------------------------------------------------------- /server/Program.cs: -------------------------------------------------------------------------------- 1 | using NetMQ.Sockets; 2 | using System; 3 | 4 | 5 | namespace ConsoleApp1 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | using (var xsubSocket = new XSubscriberSocket("@tcp://*:1234")) 12 | using (var xpubSocket = new XPublisherSocket("@tcp://*:5678")) 13 | { 14 | Console.WriteLine("Intermediary started, and waiting for messages"); 15 | 16 | // proxy messages between frontend / backend 17 | var proxy = new NetMQ.Proxy(xsubSocket, xpubSocket); 18 | 19 | // blocks indefinitely 20 | proxy.Start(); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/MarioMultiplay.java: -------------------------------------------------------------------------------- 1 | import processing.core.*; 2 | import ddf.minim.*; 3 | import java.util.Map; 4 | import java.util.Set; 5 | import java.util.HashSet; 6 | import java.util.ArrayList; 7 | import java.util.LinkedList; 8 | import java.lang.Math; 9 | import org.zeromq.ZMQ; 10 | import org.zeromq.ZMQ.Context; 11 | import org.zeromq.ZMQ.Socket; 12 | import com.google.gson.Gson; 13 | import java.util.concurrent.ConcurrentLinkedQueue; 14 | import org.yaml.snakeyaml.Yaml; 15 | 16 | 17 | public class MarioMultiplay extends PApplet { 18 | 19 | // In Eclipse, run this project as Java Application (not Applet) 20 | public static void main(String[] args) { 21 | String[] a = { "MarioMultiplay" }; 22 | PApplet.runSketch(a, new MarioMultiplay()); 23 | } 24 | 25 | public void settings() { 26 | size(WIDTH, HEIGHT, P2D); 27 | } 28 | 29 | 30 | // ============================= NEW FILE ================================== 31 | 32 | 33 | enum GAME_STATE { 34 | PLAYING, 35 | WIN, 36 | LOSE 37 | } 38 | 39 | // Global constants 40 | 41 | int FRAME_RATE = 30; 42 | int FRAMES_PER_SHOT = FRAME_RATE / 10; 43 | int WIDTH = 640; 44 | int HEIGHT = 432; 45 | int ASSET_SCALE = 2; 46 | float GRAVITY = 2.0f; 47 | float MARIO_JUMP_FORCE = 19.0f; 48 | float BOOST_JUMP_FORCE = 11.3f; 49 | float MARIO_VX_LIMIT = 9.0f; 50 | float MARIO_FORCE = 0.45f; 51 | int CAMERA_RANGE_LEFT = 170; 52 | int BLOCK_OFFSET = -16; 53 | String SERVER_HOST; 54 | String XSUB_PORT = "1234"; 55 | String XPUB_PORT = "5678"; 56 | int MY_MARIO_NUM; 57 | String ROOM_NUM; 58 | float MY_FRICTIONAL_FORCE = 0.4f; // 0.4 for normal world. 0.1 for ice world. 59 | boolean SHOW_PACKET_INDICATOR; 60 | boolean PACKET_FRUGAL = true; 61 | 62 | // Global variables 63 | 64 | GAME_STATE gameState = GAME_STATE.PLAYING; 65 | long frame = 0; 66 | Set pendingKeyboardEvents; 67 | boolean needSync = true; 68 | 69 | // Global objects 70 | 71 | Mario marioMe; 72 | Mario marioRival; 73 | Mario mario0; 74 | Mario mario1; 75 | Sprite bgImgSprite; 76 | Sprite princessSprite; 77 | BlockManager blockManager; 78 | CollisionDetector collisionDetector; 79 | EnemyCollisionDetector enemyCD; 80 | NetworkManager networkManager; 81 | UIManger uiManager; 82 | // Audio ------------- START 83 | AudioPlayer player; 84 | Minim minim; // Audio context 85 | // Audio ------------- END 86 | Gson gson; 87 | 88 | Context pubContext; 89 | Socket pubSocket; 90 | ConcurrentLinkedQueue msgQueue; 91 | 92 | 93 | public void setup() { 94 | background(51); 95 | frameRate(FRAME_RATE); 96 | 97 | // Config file 98 | Yaml yaml = new Yaml(); 99 | String[] lines = loadStrings("config.yml"); 100 | StringBuilder sb = new StringBuilder(); 101 | for (String line: lines) sb.append(line).append("\n"); 102 | String document = sb.toString(); 103 | Map confMap = (Map) yaml.load(document); 104 | ROOM_NUM = (String) confMap.get("room_num"); 105 | System.out.println("ROOM_NUM=" + ROOM_NUM); 106 | MY_MARIO_NUM = (int) confMap.get("my_mario_num"); 107 | System.out.println("MY_MARIO_NUM=" + MY_MARIO_NUM); 108 | MY_FRICTIONAL_FORCE = (float) (double) confMap.get("my_frictional_force"); 109 | System.out.println("MY_FRICTIONAL_FORCE=" + MY_FRICTIONAL_FORCE); 110 | SERVER_HOST = (String) confMap.get("server_host"); 111 | System.out.println("SERVER_HOST=" + SERVER_HOST); 112 | SHOW_PACKET_INDICATOR = (boolean) confMap.get("show_packet_indicator"); 113 | System.out.println("SHOW_PACKET_INDICATOR=" + SHOW_PACKET_INDICATOR); 114 | PACKET_FRUGAL = (boolean) confMap.get("packet_frugal"); 115 | System.out.println("PACKET_FRUGAL=" + PACKET_FRUGAL); 116 | 117 | // Backgroud image 118 | bgImgSprite = new SharedSprite("img/background_", 1); 119 | 120 | // Princess 121 | princessSprite = new SharedSprite("img/princess_standing_", 1); 122 | 123 | // Block manager 124 | blockManager = new BlockManager(); 125 | 126 | // Collision detector 127 | collisionDetector = new CollisionDetector(); 128 | enemyCD = new EnemyCollisionDetector(); 129 | 130 | // Well, our main character appears 131 | marioMe = new Mario(false); // Color 132 | marioRival = new Mario(true); // Black and White 133 | 134 | // marioMe, marioRival 135 | if (MY_MARIO_NUM == 0) { 136 | mario0 = marioMe; 137 | mario1 = marioRival; 138 | } else { 139 | mario1 = marioMe; 140 | mario0 = marioRival; 141 | } 142 | // Mario1 positioned at the right of mario0 143 | mario1.x = mario0.x + 300; 144 | 145 | // BGM 146 | minim = new Minim(this); 147 | player = minim.loadFile("Supermario_BGM_Overworld.mp3"); 148 | player.play(); 149 | 150 | // Network manager 151 | networkManager = new NetworkManager(); 152 | 153 | // UI manager 154 | uiManager = new UIManger(); 155 | 156 | // Gson 157 | gson = new Gson(); 158 | 159 | // Publisher 160 | pubContext = ZMQ.context(1); 161 | pubSocket = pubContext.socket(ZMQ.PUB); 162 | pubSocket.connect("tcp://" + SERVER_HOST + ":" + XSUB_PORT); 163 | 164 | // Message queue 165 | msgQueue = new ConcurrentLinkedQueue(); 166 | 167 | // Keyboard event queue 168 | pendingKeyboardEvents = new HashSet(); 169 | 170 | // Subscriber thread mst be launched after msgQueue is initialized! 171 | new Thread(new Subscriber()).start(); 172 | } 173 | 174 | 175 | public void stop() { 176 | // Audio player 177 | player.close(); 178 | minim.stop(); 179 | 180 | // ZeroMQ publisher 181 | pubSocket.close(); 182 | pubContext.term(); 183 | 184 | super.stop(); 185 | } 186 | 187 | 188 | public void draw() { 189 | processUserInput(); 190 | 191 | processGameLogic(); 192 | 193 | doNetworkStuff(); 194 | 195 | uiManager.update(); 196 | 197 | render(); 198 | 199 | ++frame; 200 | } 201 | 202 | 203 | // ============================= USER INPUT ================================ 204 | 205 | 206 | public void processUserInput() { 207 | marioMe.processKeyboard(); 208 | } 209 | 210 | /** 211 | * Called once every time a key is pressed. The key that was pressed is 212 | * stored in the key variable. 213 | */ 214 | public void keyPressed() { 215 | marioMe.onKeyPress(key); 216 | } 217 | 218 | /** 219 | * Called once every time a key is released. The key that was released will 220 | * be stored in the 'key' member variable of PApplet. 221 | */ 222 | public void keyReleased() { 223 | marioMe.onKeyRelease(key); 224 | } 225 | 226 | 227 | // ============================= GAME LOGIC ================================ 228 | 229 | 230 | public void processGameLogic() { 231 | // Game over? 232 | if (gameState != GAME_STATE.PLAYING) return; 233 | 234 | // Update the shared sprites 235 | blockManager.spriteGold.update(); 236 | 237 | // Do the simulation 238 | marioMe.update(); 239 | marioRival.update(); 240 | } 241 | 242 | 243 | // ======================== NETWORK COMMUNICATION =========================== 244 | 245 | 246 | public void doNetworkStuff() { 247 | networkWrite(); // First, send my mario's state, which is already set, 248 | networkRead(); // then set up ghost mario's state from what received. 249 | } 250 | 251 | public void networkRead() { 252 | // Game over? 253 | if (gameState != GAME_STATE.PLAYING) return; 254 | 255 | // Process all unread msgs 256 | ArrayList unreadMsgs = networkManager.getUnreadMsgs(); 257 | for (String msg: unreadMsgs) { 258 | if (gameState == GAME_STATE.PLAYING) { 259 | processMsg(msg); 260 | uiManager.addInPacket(); 261 | } else { // Game over 262 | ; // Throw packet 263 | } 264 | } 265 | } 266 | 267 | public void networkWrite() { 268 | // Game over? 269 | if (gameState != GAME_STATE.PLAYING) return; 270 | 271 | // Need sync? 272 | if (PACKET_FRUGAL && ! needSync) return; 273 | 274 | Packet packet = networkManager.makeMarioStatePacket(marioMe); 275 | String packetStr = networkManager.packetToString(packet); 276 | String channel = ROOM_NUM + "CHANNEL_MARIO_" + MY_MARIO_NUM; 277 | pubSocket.sendMore(channel); 278 | pubSocket.send(packetStr); 279 | 280 | // Notify UIManager 281 | uiManager.addOutPacket(); 282 | } 283 | 284 | public void processMsg(String msg) { 285 | if (frame < 100) System.out.println("Frame " + frame + ": " + msg); 286 | 287 | Packet packet = networkManager.stringToPacket(msg); 288 | 289 | // Judge packet type 290 | switch (packet.type) { 291 | case "GAME_OVER": 292 | if (packet.whoWon == MY_MARIO_NUM) { // Win 293 | gameState = GAME_STATE.WIN; 294 | } else if (packet.whoWon == (1 - MY_MARIO_NUM)) { // Lose 295 | gameState = GAME_STATE.LOSE; 296 | } 297 | break; 298 | case "MARIO_STATE": 299 | // Setting mario state 300 | marioRival.x = packet.x; 301 | marioRival.y = packet.y; 302 | marioRival.vx = packet.vx; 303 | marioRival.vy = packet.vy; 304 | marioRival.ax = packet.ax; 305 | marioRival.ay = packet.ay; 306 | switch (packet.motionState) { 307 | case "STANDING": marioRival.motionState = MotionState.STANDING; 308 | break; 309 | case "RUNNING": marioRival.motionState = MotionState.RUNNING; 310 | break; 311 | case "JUMPING": marioRival.motionState = MotionState.JUMPING; 312 | break; 313 | case "FALLING": marioRival.motionState = MotionState.FALLING; 314 | break; 315 | default: 316 | break; 317 | } 318 | switch (packet.faceState) { 319 | case "FACE_FRONT": marioRival.faceState = MarioFace.FACE_FRONT; 320 | break; 321 | case "FACE_LEFT": marioRival.faceState = MarioFace.FACE_LEFT; 322 | break; 323 | case "FACE_RIGHT": marioRival.faceState = MarioFace.FACE_RIGHT; 324 | break; 325 | default: 326 | break; 327 | } 328 | marioRival.boostJumping = packet.boostJumping; 329 | marioRival.jumpPressed = packet.jumpPressed; 330 | switch (packet.arrowX) { 331 | case "LEFT": marioRival.arrowX = MarioArrowX.LEFT; 332 | break; 333 | case "RIGHT": marioRival.arrowX = MarioArrowX.RIGHT; 334 | break; 335 | default: marioRival.arrowX = MarioArrowX.NONE; 336 | break; 337 | } 338 | switch (packet.arrowY) { 339 | case "UP": marioRival.arrowY = MarioArrowY.UP; 340 | break; 341 | case "DOWN": marioRival.arrowY = MarioArrowY.DOWN; 342 | break; 343 | default: marioRival.arrowY = MarioArrowY.NONE; 344 | break; 345 | } 346 | marioRival.frictionalForce = packet.frictionalForce; 347 | break; 348 | default: ; 349 | break; 350 | } 351 | } 352 | 353 | 354 | public void gameOverWinning() { 355 | gameState = GAME_STATE.WIN; 356 | 357 | // Send GAME_OVER packet to the rival 358 | Packet packet = networkManager.makeGameOverPacket(MY_MARIO_NUM); 359 | String packetStr = networkManager.packetToString(packet); 360 | String channel = ROOM_NUM + "CHANNEL_MARIO_" + MY_MARIO_NUM; 361 | pubSocket.sendMore(channel); 362 | pubSocket.send(packetStr); 363 | } 364 | 365 | public void resetGame() { 366 | gameState = GAME_STATE.PLAYING; 367 | marioMe.x = 150 + MY_MARIO_NUM * 300; 368 | marioMe.y = 0; 369 | marioMe.vx = 0; 370 | marioMe.vy = 0; 371 | marioMe.ax = 0; 372 | marioMe.ay = GRAVITY; 373 | needSync = true; 374 | msgQueue.clear(); 375 | } 376 | 377 | 378 | // ============================= RENDERING ================================= 379 | 380 | 381 | public void render() { 382 | // Draw Background 383 | drawBackground(); 384 | 385 | // Draw map 386 | drawMap(); 387 | 388 | // Draw the Marios' sprite 389 | noFill(); 390 | noStroke(); 391 | marioRival.display(); 392 | marioMe.display(); 393 | 394 | // Draw UI 395 | uiManager.display(); 396 | 397 | // Game Over? 398 | textSize(50); 399 | fill(255, 174, 201); 400 | textAlign(CENTER, CENTER); 401 | if (gameState == GAME_STATE.WIN) { 402 | String gameStateStr = "WIN"; 403 | text(gameStateStr, WIDTH / 2, HEIGHT / 2); 404 | } else if (gameState == GAME_STATE.LOSE) { 405 | String gameStateStr = "LOSE"; 406 | text(gameStateStr, WIDTH / 2, HEIGHT / 2); 407 | } 408 | } 409 | 410 | public void drawBackground() { 411 | fill(107, 140, 255); 412 | noStroke(); 413 | rect(0, 0, WIDTH, HEIGHT); 414 | bgImgSprite.display(-1, 8); 415 | princessSprite.display(6639, 384 - princessSprite.getHeight()); 416 | } 417 | 418 | public void drawMap() { 419 | blockManager.display(); 420 | } 421 | 422 | 423 | // =============================== NEW FILE ================================== 424 | 425 | 426 | enum MotionState { 427 | STANDING, RUNNING, JUMPING, FALLING, 428 | } 429 | 430 | enum MarioFace { 431 | FACE_LEFT, FACE_RIGHT, FACE_FRONT 432 | } 433 | 434 | enum MarioArrowX { 435 | NONE, 436 | LEFT, 437 | RIGHT 438 | } 439 | 440 | enum MarioArrowY { 441 | NONE, 442 | UP, 443 | DOWN 444 | } 445 | 446 | class Mario { 447 | // Space state 448 | int width, height; 449 | float x = 150, y = 0; 450 | float vx = 0; 451 | float vy = 0; 452 | float ax = 0; 453 | float ay = GRAVITY; 454 | 455 | // Force state 456 | float frictionalForce = 0; 457 | 458 | // Motion state 459 | MotionState motionState = MotionState.JUMPING; 460 | MarioFace faceState = MarioFace.FACE_RIGHT; 461 | 462 | // Keyboard State 463 | MarioArrowX arrowX = MarioArrowX.NONE; 464 | MarioArrowY arrowY = MarioArrowY.NONE; 465 | boolean jumpPressed = false; 466 | 467 | boolean boostJumping = false; 468 | 469 | // Sprite state 470 | Sprite currentSprite = null; 471 | 472 | // Sprites 473 | Sprite marioStanding, marioRunning, marioJumping, marioFalling; 474 | 475 | Camera camera; 476 | 477 | boolean isGhost = false; 478 | 479 | Mario(boolean blackWhite) { 480 | // Load sprites 481 | if (! blackWhite) { 482 | marioStanding = new Sprite("img/mario_standing_", 1, true); 483 | marioRunning = new Sprite("img/mario_running_", 4, true); 484 | marioJumping = new Sprite("img/mario_jumping_", 1, true); 485 | marioFalling = new Sprite("img/mario_falling_", 2, true); 486 | } else { 487 | marioStanding = new Sprite("img/bw_mario_standing_", 1, true); 488 | marioRunning = new Sprite("img/bw_mario_running_", 4, true); 489 | marioJumping = new Sprite("img/bw_mario_jumping_", 1, true); 490 | marioFalling = new Sprite("img/bw_mario_falling_", 2, true); 491 | 492 | isGhost = true; 493 | } 494 | 495 | width = marioStanding.getWidth(); 496 | height = marioStanding.getHeight(); 497 | 498 | frictionalForce = MY_FRICTIONAL_FORCE; 499 | 500 | // Make an own camera 501 | camera = new Camera(); 502 | } 503 | 504 | public void update() { 505 | _updateMotion(); 506 | _updateSpeed(); 507 | _updatePosition(); 508 | } 509 | 510 | public void display() { 511 | // Select sprite 512 | if (motionState == MotionState.FALLING) currentSprite = marioFalling; 513 | else if (motionState == MotionState.JUMPING) currentSprite = marioJumping; 514 | else if (motionState == MotionState.RUNNING) currentSprite = marioRunning; 515 | else if (motionState == MotionState.STANDING) currentSprite = marioStanding; 516 | 517 | // Select face direction 518 | if (faceState == MarioFace.FACE_LEFT) { 519 | currentSprite.display(x, y, true); 520 | } else 521 | currentSprite.display(x, y); 522 | } 523 | 524 | public float getCenterX() { 525 | return x + 0.5f * width; 526 | } 527 | 528 | // Top two points of the hitbox 529 | public Coordinate[] getHitboxTop() { 530 | Coordinate vertx1 = new Coordinate(x + 2, y); 531 | Coordinate vertx2 = new Coordinate(x + width - 2, y); 532 | return new Coordinate[]{vertx1, vertx2}; 533 | } 534 | 535 | // Bottom two points of the hitbox 536 | public Coordinate[] getHitboxBottom() { 537 | Coordinate vertx1 = new Coordinate(x + 2, y + height); 538 | Coordinate vertx2 = new Coordinate(x + width - 2, y + height); 539 | return new Coordinate[]{vertx1, vertx2}; 540 | } 541 | 542 | public Hitbox getHitboxHead() { 543 | Hitbox hitbox = new Hitbox(); 544 | hitbox.topLeft = new Coordinate(x + 2, y); 545 | hitbox.topRight = new Coordinate(x + width - 2, y); 546 | hitbox.bottomLeft = new Coordinate(x + 2, y + height - 25); 547 | hitbox.bottomRight = new Coordinate(x + width - 2, y + height - 25); 548 | return hitbox; 549 | } 550 | 551 | public Hitbox getHitbox() { 552 | Hitbox hitbox = new Hitbox(); 553 | hitbox.topLeft = new Coordinate(x + 2, y); 554 | hitbox.topRight = new Coordinate(x + width - 2, y); 555 | hitbox.bottomLeft = new Coordinate(x + 2, y + height); 556 | hitbox.bottomRight = new Coordinate(x + width - 2, y + height); 557 | return hitbox; 558 | } 559 | 560 | public void onKeyPress(char key) { 561 | if (key == 'c' || key == 'C') { 562 | pendingKeyboardEvents.add("_jump"); 563 | } else if (key == 'x' || key == '#') { 564 | ; 565 | } else if (key == ENTER) { 566 | if (gameState != GAME_STATE.PLAYING) resetGame(); 567 | }else if (key == CODED) { 568 | if (keyCode == UP) { 569 | pendingKeyboardEvents.add("_up"); 570 | } else if (keyCode == DOWN) { 571 | pendingKeyboardEvents.add("_down"); 572 | } 573 | if (keyCode == LEFT) { 574 | pendingKeyboardEvents.add("_left"); 575 | } else if (keyCode == RIGHT) { 576 | pendingKeyboardEvents.add("_right"); 577 | } 578 | } 579 | } 580 | 581 | public void onKeyRelease(char key) { 582 | if (key == 'c' || key == 'C') { 583 | pendingKeyboardEvents.add("_release_jump"); 584 | } else if (key == 'x' || key == '#') { 585 | ; 586 | } else if (key == CODED) { 587 | if (keyCode == UP) { 588 | pendingKeyboardEvents.add("_release_up"); 589 | } else if (keyCode == DOWN) { 590 | pendingKeyboardEvents.add("_release_down"); 591 | } 592 | if (keyCode == LEFT) { 593 | pendingKeyboardEvents.add("_release_left"); 594 | } else if (keyCode == RIGHT) { 595 | pendingKeyboardEvents.add("_release_right"); 596 | } 597 | } 598 | } 599 | 600 | public void processKeyboard() { 601 | if (pendingKeyboardEvents.isEmpty()) needSync = false; 602 | else needSync = true; 603 | 604 | for (String event: pendingKeyboardEvents) { 605 | switch (event) { 606 | case "_jump": _jump(); 607 | break; 608 | case "_left": _left(); 609 | break; 610 | case "_right": _right(); 611 | break; 612 | case "_up": arrowY = MarioArrowY.UP; 613 | break; 614 | case "_down": arrowY = MarioArrowY.DOWN; 615 | break; 616 | case "_release_jump": jumpPressed = false; 617 | break; 618 | case "_release_left": if (arrowX == MarioArrowX.LEFT) arrowX = MarioArrowX.NONE;; 619 | break; 620 | case "_release_right": if (arrowX == MarioArrowX.RIGHT) arrowX = MarioArrowX.NONE; 621 | break; 622 | case "_release_up": if (arrowY == MarioArrowY.UP) arrowY = MarioArrowY.NONE; 623 | break; 624 | case "_release_down": if (arrowY == MarioArrowY.DOWN) arrowY = MarioArrowY.NONE; 625 | break; 626 | default: ; 627 | break; 628 | } 629 | } 630 | pendingKeyboardEvents.clear(); 631 | } 632 | 633 | public void _jump() { 634 | if (motionState != MotionState.JUMPING) { 635 | motionState = MotionState.JUMPING; 636 | vy = -MARIO_JUMP_FORCE; // Bounce up! 위로 튕겨주다! 637 | } 638 | jumpPressed = true; 639 | } 640 | 641 | public void _boostJump() { 642 | if (! boostJumping) { 643 | boostJumping = true; 644 | vy -= BOOST_JUMP_FORCE; 645 | } 646 | } 647 | 648 | public void _disJump() { 649 | // Release the jumping state 650 | if (motionState == MotionState.JUMPING) motionState = MotionState.STANDING; 651 | boostJumping = false; 652 | vy = 0; 653 | } 654 | 655 | public void _friction() { 656 | // Damping to break 657 | if (vx > 0 && motionState != MotionState.JUMPING) ax = -frictionalForce; 658 | else if (vx < 0 && motionState != MotionState.JUMPING) ax = frictionalForce; 659 | } 660 | 661 | public void _left() { 662 | // Turn the face to left 663 | arrowX = MarioArrowX.LEFT; 664 | 665 | faceState = MarioFace.FACE_LEFT; 666 | // Run, if were standing. 서있던거면 달려라 667 | if (motionState == MotionState.STANDING) motionState = MotionState.RUNNING; 668 | 669 | // Give a force to left 670 | if (vx > 0) vx = 0; 671 | ax = -MARIO_FORCE; 672 | } 673 | 674 | public void _right() { 675 | arrowX = MarioArrowX.RIGHT; 676 | 677 | // Turn the face to right 678 | faceState = MarioFace.FACE_RIGHT; 679 | // Run, if were standing. 서있던거면 달려라 680 | if (motionState == MotionState.STANDING) motionState = MotionState.RUNNING; 681 | 682 | // Give a force to right 683 | if (vx < 0) vx = 0; 684 | ax = MARIO_FORCE; 685 | } 686 | 687 | public void _updateMotion() { 688 | if (arrowX == MarioArrowX.LEFT) { 689 | faceState = MarioFace.FACE_LEFT; 690 | 691 | if (motionState == MotionState.STANDING) { // 달리기 로직 692 | motionState = MotionState.RUNNING; 693 | } 694 | } else if (arrowX == MarioArrowX.RIGHT) { 695 | faceState = MarioFace.FACE_RIGHT; 696 | if (motionState == MotionState.STANDING) { // 달리기 로직 697 | motionState = MotionState.RUNNING; 698 | } 699 | } else if (arrowX == MarioArrowX.NONE) { 700 | // 占쏙옙占쌩깍옙 占쏙옙占쏙옙 701 | if (motionState == MotionState.RUNNING) motionState = MotionState.STANDING; 702 | _friction(); 703 | } 704 | } 705 | 706 | public void _updateSpeed() { 707 | // Y-axis 708 | vy += ay; 709 | // Boost jump 710 | if (vy < 0 && vy > -4 && jumpPressed && ! boostJumping) _boostJump(); 711 | 712 | // X-axis 713 | float previousVx = vx; 714 | vx += ax; 715 | // Limit speed 716 | if (Math.abs(vx) > MARIO_VX_LIMIT) { 717 | if (vx > 0) { 718 | vx = MARIO_VX_LIMIT; 719 | } else { 720 | vx = -MARIO_VX_LIMIT; 721 | } 722 | } else if (Math.abs(vx) < MARIO_FORCE) { 723 | vx = 0; 724 | ax = 0; 725 | } else if (previousVx * vx < 0) { // Symbol changed 726 | vx = 0; 727 | ax = 0; 728 | } 729 | } 730 | 731 | public void _updatePosition() { 732 | // Game Over? 733 | if (gameState != GAME_STATE.PLAYING) return; 734 | 735 | // Update X 736 | float previousX = x; 737 | x += vx; 738 | if (x < 0) x = 0; 739 | if (x > 7000) x = 7000; 740 | 741 | // Collision detection: X 742 | if (collisionDetector.left(this) || collisionDetector.right(this)){ 743 | x = previousX; 744 | vx = 0; 745 | } 746 | 747 | // Update Y 748 | float previousY = y; 749 | y += vy; 750 | 751 | // Collision detection: Y 752 | if (collisionDetector.up(this)) { 753 | y = previousY; 754 | vy = 0; 755 | } 756 | if (y > HEIGHT - mario0.height || collisionDetector.down(this) ) { 757 | y = previousY; 758 | vy = 0; 759 | _disJump(); 760 | } 761 | 762 | // Enemy Collision Detection 763 | if (! isGhost) { // FTS: Favor The Shooter. 고스트는 밟기 판정 안함 764 | if (enemyCD.isTrample(getHitboxBottom(), marioRival.getHitboxHead(), this) 765 | == HitType.ENEMY) { 766 | gameOverWinning(); 767 | } 768 | } 769 | 770 | // Camera following 771 | if (getCenterX() - camera.getCenterX() > 0) { // Mario too right 772 | camera.x = getCenterX() - 0.5f * camera.width; 773 | } else if (camera.getCenterX() - getCenterX() > CAMERA_RANGE_LEFT) { // Mario too left 774 | camera.x = getCenterX() + CAMERA_RANGE_LEFT - 0.5f * camera.width; 775 | if (camera.x < 0) camera.x = 0; 776 | } 777 | } 778 | } 779 | 780 | // ============================== NEW FILE ================================== 781 | 782 | int framesPerShot = FRAMES_PER_SHOT; 783 | 784 | /** 785 | * Class for animating a sequence of GIFs 786 | */ 787 | class Sprite { 788 | PImage[] images; 789 | PImage[] mirroredImages; 790 | int imageCount; 791 | int frame; 792 | 793 | Sprite(String imagePrefix, int count) { 794 | imageCount = count; 795 | images = new PImage[imageCount]; 796 | 797 | for (int i = 0; i < imageCount; ++i) { 798 | // Use nf() to number format 'i' into four digits 799 | String filename = imagePrefix + nf(i, 4) + ".gif"; 800 | PImage img = loadImage(filename); 801 | images[i] = _enlarge(img, 2); 802 | } 803 | } 804 | 805 | Sprite(String imagePrefix, int count, boolean hasMirrored) { 806 | 807 | imageCount = count; 808 | images = new PImage[imageCount]; 809 | 810 | for (int i = 0; i < imageCount; ++i) { 811 | // Use nf() to number format 'i' into four digits 812 | String filename = imagePrefix + nf(i, 4) + ".gif"; 813 | PImage img = loadImage(filename); 814 | images[i] = _enlarge(img, 2); 815 | } 816 | 817 | if (hasMirrored) { 818 | mirroredImages = new PImage[imageCount]; 819 | for (int i = 0; i < imageCount; ++i) { 820 | mirroredImages[i] = _flip(images[i]); 821 | } 822 | } 823 | } 824 | 825 | public void display(float xpos, float ypos) { // 월드 좌표 받고 826 | frame = (frame + 1) % (imageCount * framesPerShot); 827 | int shotIndex = frame / framesPerShot; 828 | image(images[shotIndex], xpos - marioMe.camera.x, ypos, images[shotIndex].width, 829 | images[shotIndex].height); // 카메라 좌표계로 그려줌 830 | } 831 | 832 | public void display(float xpos, float ypos, boolean reverse) { 833 | if (! reverse) display(xpos - marioMe.camera.x, ypos); 834 | 835 | frame = (frame + 1) % (imageCount * framesPerShot); 836 | int shotIndex = frame / framesPerShot; 837 | image(mirroredImages[shotIndex], xpos - marioMe.camera.x, ypos, mirroredImages[shotIndex].width, 838 | mirroredImages[shotIndex].height); 839 | } 840 | 841 | public int getWidth() { 842 | return images[0].width; 843 | } 844 | 845 | public int getHeight() { 846 | return images[0].height; 847 | } 848 | 849 | public PImage _enlarge(PImage image, int multiple) { 850 | PImage newImg; 851 | newImg = createImage(image.width * multiple, image.height * multiple, ARGB); 852 | for (int i = 0; i < image.width; ++i) { 853 | for (int j = 0; j < image.height; ++j) { 854 | newImg.set(2 * i, 2 * j, image.get(i, j)); 855 | newImg.set(2 * i, 2 * j + 1, image.get(i, j)); 856 | newImg.set(2 * i + 1, 2 * j, image.get(i, j)); 857 | newImg.set(2 * i + 1, 2 * j + 1, image.get(i, j)); 858 | } 859 | } 860 | 861 | return newImg; 862 | } 863 | 864 | public PImage _flip(PImage image) { 865 | PImage reverse; 866 | reverse = createImage(image.width, image.height, ARGB); 867 | 868 | for (int i = 0; i < image.width; ++i) { 869 | for (int j = 0; j < image.height; ++j) { 870 | int xPixel, yPixel; 871 | xPixel = image.width - 1 - i; 872 | yPixel = j; 873 | reverse.pixels[yPixel * image.width + xPixel] = image.pixels[j * image.width + i]; 874 | } 875 | } 876 | return reverse; 877 | } 878 | 879 | } 880 | 881 | class SharedSprite extends Sprite { 882 | 883 | public SharedSprite(String imagePrefix, int count) { 884 | super(imagePrefix, count); 885 | } 886 | 887 | public SharedSprite(String imagePrefix, int count, boolean hasMirrored) { 888 | super(imagePrefix, count, hasMirrored); 889 | } 890 | 891 | public void display(float xpos, float ypos) { 892 | int shotIndex = frame / framesPerShot; 893 | image(images[shotIndex], xpos - marioMe.camera.x, ypos, 894 | images[shotIndex].width, images[shotIndex].height); 895 | } 896 | 897 | public void display(float xpos, float ypos, boolean reverse) { 898 | int shotIndex = frame / framesPerShot; 899 | 900 | if (! reverse) { 901 | image(images[shotIndex], xpos - marioMe.camera.x, ypos, 902 | images[shotIndex].width, images[shotIndex].height); 903 | } else { 904 | image(mirroredImages[shotIndex], xpos - marioMe.camera.x, ypos, 905 | mirroredImages[shotIndex].width, mirroredImages[shotIndex].height); 906 | } 907 | } 908 | 909 | public void update() { 910 | frame = (frame + 1) % (imageCount * framesPerShot); 911 | } 912 | } 913 | 914 | class TextSprite { 915 | private float x, y; 916 | private float vy; 917 | private String _text; 918 | private int size; 919 | int r; 920 | int g; 921 | int b; 922 | 923 | public TextSprite(String text, int size, float x, float y, float vy) { 924 | this.x = x; 925 | this.y = y; 926 | this._text = text; 927 | this.size = size; 928 | this.vy = vy; 929 | 930 | this.r = (int) random(255); 931 | this.g = (int) random(255); 932 | this.b = (int) random(255); 933 | } 934 | 935 | public void update() { 936 | this.y += vy; 937 | } 938 | 939 | public void display() { 940 | textSize(size); 941 | fill(0, 0, 0); 942 | text(_text, x + 1, y + 1); 943 | if (PACKET_FRUGAL) fill(255, 255, 255); 944 | else fill(r, g, b); 945 | textAlign(CENTER, CENTER); 946 | text(_text, x, y); 947 | } 948 | } 949 | 950 | 951 | // =========================== NEW FILE ================================== 952 | 953 | 954 | class Camera { 955 | int width, height; // Camera sight 956 | float x = 0, y = 0; // Camera's absolute coordinate 957 | 958 | Camera() { 959 | width = WIDTH; 960 | height = HEIGHT; 961 | } 962 | 963 | float getCenterX() { 964 | return x + 0.5f * width; 965 | } 966 | } 967 | 968 | 969 | // =========================== NEW FILE ================================== 970 | 971 | 972 | class Block { 973 | int x = 0, y = 0; 974 | int xB = 0, yB = 0; 975 | char blockType; 976 | boolean visible = true; 977 | SharedSprite sprite; 978 | 979 | public Block(int xB, int yB, char blockType) { 980 | this.xB = xB; 981 | this.yB = yB; 982 | this.x = 16 * ASSET_SCALE * xB + BLOCK_OFFSET; 983 | this.y = 16 * ASSET_SCALE * yB; 984 | 985 | this.visible = true; 986 | this.blockType = blockType; 987 | } 988 | 989 | public void display() { 990 | if (sprite != null) { 991 | sprite.display(x, y); // 월드 좌표 받음 992 | } 993 | } 994 | } 995 | 996 | class Coordinate { 997 | float x = 0, y = 0; 998 | 999 | public Coordinate(float x, float y) { 1000 | this.x = x; 1001 | this.y = y; 1002 | } 1003 | } 1004 | 1005 | class BlockManager { 1006 | SharedSprite spriteGround; 1007 | SharedSprite spriteBrick; 1008 | SharedSprite spriteBlock; 1009 | SharedSprite spriteQuestion; 1010 | SharedSprite spriteGold; 1011 | ArrayList> blocks = new ArrayList>(); 1012 | 1013 | public BlockManager() { 1014 | // Load sprites 1015 | spriteGround = new SharedSprite("img/ground_", 1); 1016 | spriteBlock = new SharedSprite("img/block_", 1); 1017 | spriteBrick = new SharedSprite("img/brick_", 1); 1018 | spriteQuestion = new SharedSprite("img/question_", 1); 1019 | spriteGold = new SharedSprite("img/gold_", 4); 1020 | 1021 | // Read map.txt 1022 | String[] lines = loadStrings("map.txt"); 1023 | // Make blocks 1024 | println("Start to make blocks.."); 1025 | for (int i = 0; i < lines.length; ++i) { 1026 | String line = lines[i]; 1027 | char[] charArray = line.toCharArray(); 1028 | ArrayList blockRow = new ArrayList(); 1029 | for (int j = 0; j < charArray.length; ++j) { 1030 | char c = charArray[j]; 1031 | Block block = new Block(j, i, c); 1032 | switch (c) { 1033 | case ' ' : 1034 | break; 1035 | case '#' : ; block.sprite = spriteGround; // Ground 1036 | break; 1037 | case 'B' : block.sprite = spriteBlock; // Block 1038 | break; 1039 | case 'R' : block.sprite = spriteBrick; // Block 1040 | break; 1041 | case '?' : block.sprite = spriteQuestion; // Question mark 1042 | break; 1043 | case 'O' : block.sprite = spriteGold; // Gold 1044 | break; 1045 | default : 1046 | break; 1047 | } 1048 | 1049 | blockRow.add(block); 1050 | print(c); 1051 | } 1052 | blocks.add(blockRow); 1053 | println(); 1054 | } 1055 | } 1056 | 1057 | public void display() { 1058 | int xStart; 1059 | if ((x2bX(marioMe.camera.getCenterX()) - 11) < 0) xStart = 0; 1060 | else xStart = x2bX(marioMe.camera.getCenterX()) - 11; 1061 | int xEnd = xStart + 23; 1062 | 1063 | for (int i = 0; i < blocks.size(); ++i) { 1064 | for (int j = xStart; j < Math.min(xEnd + 1, blocks.get(i).size()); ++j) { 1065 | Block block = blocks.get(i).get(j); 1066 | block.display(); 1067 | } 1068 | } 1069 | } 1070 | 1071 | public Coordinate block2Coor(int bX, int bY) { 1072 | Coordinate coordinate = new Coordinate(16 * ASSET_SCALE * bX, 1073 | 16 * ASSET_SCALE * bY); 1074 | return coordinate; 1075 | } 1076 | 1077 | public int y2bY(float y) { 1078 | return (int) y / (16 * ASSET_SCALE); 1079 | } 1080 | 1081 | public int x2bX(float x) { 1082 | return (int) (x - BLOCK_OFFSET) / (16 * ASSET_SCALE); 1083 | } 1084 | } 1085 | 1086 | 1087 | // ============================ NEW FILE ================================== 1088 | 1089 | 1090 | class CollisionDetector { 1091 | public boolean up(Mario mario) { 1092 | if ((int)mario.y <= 0) return false; 1093 | 1094 | int step = 24; 1095 | for (int i = 0; i < 2; ++i) { 1096 | float x = mario.x + i * step; 1097 | int bX = blockManager.x2bX(x); 1098 | int bY = blockManager.y2bY(mario.y); 1099 | 1100 | ArrayList blockRow = blockManager.blocks.get(bY); 1101 | if (blockRow.size() <= bX) continue; // No block 1102 | 1103 | Block block = blockRow.get(bX); 1104 | if (block == null) continue; 1105 | if (block.blockType == ' ' || block.blockType == 'O') 1106 | continue; 1107 | else return true; 1108 | } 1109 | 1110 | // 위에서 true로 미리 빠져나가지 않았다 1111 | return false; 1112 | } 1113 | 1114 | public boolean down(Mario mario) { 1115 | if ((int)mario.y <= 0) return false; 1116 | 1117 | int step = 24; 1118 | for (int i = 0; i < 2; ++i) { 1119 | float x = mario.x + i * step; 1120 | int bX = blockManager.x2bX(x); 1121 | int bY = blockManager.y2bY(mario.y + mario.height); 1122 | 1123 | ArrayList blockRow = blockManager.blocks.get(bY); 1124 | if (blockRow.size() <= bX) continue; // No block 1125 | 1126 | Block block = blockRow.get(bX); 1127 | if (block == null) continue; 1128 | if (block.blockType == ' ' || block.blockType == 'O') continue; 1129 | else return true; 1130 | } 1131 | 1132 | // 위에서 true로 미리 빠져나가지 않았다 1133 | return false; 1134 | } 1135 | 1136 | public boolean left(Mario mario) { 1137 | if ((int)mario.y <= 0) return false; 1138 | 1139 | int step = mario.height / 2; 1140 | for (int i = 0; i < 3; ++i) { 1141 | float y = mario.y + i * step; 1142 | int bX = blockManager.x2bX(mario.x); 1143 | int bY = blockManager.y2bY(y); 1144 | 1145 | ArrayList blockRow = blockManager.blocks.get(bY); 1146 | if (blockRow.size() <= bX) continue; // No block 1147 | 1148 | Block block = blockRow.get(bX); 1149 | if (block == null) continue; 1150 | if (block.blockType == ' ' || block.blockType == 'O') continue; 1151 | else return true; 1152 | } 1153 | 1154 | // 위에서 true로 미리 빠져나가지 않았다 1155 | return false; 1156 | } 1157 | 1158 | public boolean right(Mario mario) { 1159 | if ((int)mario.y <= 0) return false; 1160 | 1161 | int step = mario.height / 2; 1162 | for (int i = 0; i < 3; ++i) { 1163 | float y = mario.y + i * step; 1164 | int bX = blockManager.x2bX(mario.x + mario.width); 1165 | int bY = blockManager.y2bY(y); 1166 | 1167 | ArrayList blockRow = blockManager.blocks.get(bY); 1168 | if (blockRow.size() <= bX) continue; // No block 1169 | 1170 | Block block = blockRow.get(bX); 1171 | if (block == null) continue; 1172 | if (block.blockType == ' ' || block.blockType == 'O') continue; 1173 | else return true; 1174 | } 1175 | 1176 | // 위에서 true로 미리 빠져나가지 않았다 1177 | return false; 1178 | } 1179 | } 1180 | 1181 | 1182 | // ============================= NEW FILE ================================== 1183 | 1184 | 1185 | class Rect { 1186 | Coordinate topLeft; 1187 | Coordinate topRight; 1188 | Coordinate bottomLeft; 1189 | Coordinate bottomRight; 1190 | } 1191 | 1192 | class Hitbox extends Rect {} 1193 | 1194 | enum HitType { 1195 | NONE, 1196 | ENEMY 1197 | } 1198 | 1199 | class EnemyCollisionDetector { 1200 | public HitType isTrample(Coordinate[] myHitboxBottom, Hitbox enemyHitbox, 1201 | Mario marioMe) { 1202 | if (marioMe.vy < 0.1) return HitType.NONE; 1203 | 1204 | Coordinate[] hbVertexes = new Coordinate[]{ 1205 | enemyHitbox.topLeft, 1206 | enemyHitbox.topRight, 1207 | enemyHitbox.bottomLeft, 1208 | enemyHitbox.bottomRight 1209 | }; 1210 | for (Coordinate foot: myHitboxBottom) { 1211 | boolean isMinX = true; 1212 | for (Coordinate vertx: hbVertexes) { 1213 | if (foot.x >= vertx.x) { 1214 | isMinX = false; 1215 | break; 1216 | } 1217 | } 1218 | if (isMinX) continue; 1219 | 1220 | boolean isMaxX = true; 1221 | for (Coordinate vertx: hbVertexes) { 1222 | if (foot.x <= vertx.x) { 1223 | isMaxX = false; 1224 | break; 1225 | } 1226 | } 1227 | if (isMaxX) continue; 1228 | 1229 | boolean isMinY = true; 1230 | for (Coordinate vertx: hbVertexes) { 1231 | if (foot.y >= vertx.y) { 1232 | isMinY = false; 1233 | break; 1234 | } 1235 | } 1236 | if (isMinY) continue; 1237 | 1238 | boolean isMaxY = true; 1239 | for (Coordinate vertx: hbVertexes) { 1240 | if (foot.y <= vertx.y) { 1241 | isMaxY = false; 1242 | break; 1243 | } 1244 | } 1245 | if (isMaxY) continue; 1246 | 1247 | // Neither min nor max, return HIT 1248 | return HitType.ENEMY; 1249 | } 1250 | 1251 | // Not hit 1252 | return HitType.NONE; 1253 | } 1254 | } 1255 | 1256 | 1257 | // ============================= NEW FILE ================================== 1258 | 1259 | 1260 | class Subscriber implements Runnable { 1261 | Context context; 1262 | Socket subSocket; 1263 | 1264 | // Constructor 1265 | public Subscriber () { 1266 | context = ZMQ.context(1); 1267 | subSocket = context.socket(ZMQ.SUB); 1268 | subSocket.connect("tcp://" + SERVER_HOST + ":" + XPUB_PORT); 1269 | String channel = (ROOM_NUM + "CHANNEL_MARIO_" + String.valueOf(1 - MY_MARIO_NUM)); 1270 | // String channel = (ROOM_NUM + "CHANNEL_MARIO_0"); 1271 | 1272 | subSocket.subscribe(channel.getBytes(ZMQ.CHARSET)); 1273 | } 1274 | 1275 | public void run() { 1276 | while (!Thread.currentThread().isInterrupted()) { 1277 | /* It read synchronously, so no need to sleep(). 1278 | * It is copied from the official sample code. 1279 | */ 1280 | 1281 | // Read envelope with address 1282 | String channel = subSocket.recvStr(); 1283 | // Read message contents 1284 | String contents = subSocket.recvStr(); 1285 | //System.out.println("Received from: " + channel + " Contents: " + contents); 1286 | msgQueue.add(contents); 1287 | } 1288 | } 1289 | 1290 | public void finallize() throws Throwable { 1291 | try{ 1292 | if (subSocket != null) subSocket.close(); 1293 | if (context != null) context.term(); 1294 | } 1295 | finally { 1296 | super.finalize(); 1297 | } 1298 | } 1299 | } 1300 | 1301 | 1302 | // ============================= NEW FILE ================================== 1303 | 1304 | 1305 | class Packet { 1306 | public String type; // "MARIO_STATE" / "GAME_OVER" 1307 | public int whoWon; // 0, 1, -1 1308 | public float x; 1309 | public float y; 1310 | public float vx; 1311 | public float vy; 1312 | public float ax; 1313 | public float ay; 1314 | public String motionState; 1315 | public String faceState; 1316 | public boolean boostJumping; 1317 | public boolean jumpPressed; 1318 | public String arrowX; 1319 | public String arrowY; 1320 | public float frictionalForce; 1321 | } 1322 | 1323 | 1324 | // ============================= NEW FILE ================================== 1325 | 1326 | 1327 | class NetworkManager { 1328 | public ArrayList getUnreadMsgs() { 1329 | ArrayList unreadMsgs = new ArrayList(); 1330 | String msg; 1331 | while ((msg = msgQueue.poll()) != null) { 1332 | unreadMsgs.add(msg); 1333 | } 1334 | return unreadMsgs; 1335 | } 1336 | 1337 | public Packet makeMarioStatePacket(Mario mario) { 1338 | Packet packet = new Packet(); 1339 | packet.type = "MARIO_STATE"; 1340 | packet.whoWon = -1; // For none 1341 | packet.x = mario.x; 1342 | packet.y = mario.y; 1343 | packet.vx = mario.vx; 1344 | packet.vy = mario.vy; 1345 | packet.ax = mario.ax; 1346 | packet.ay = mario.ay; 1347 | String motionState = ""; 1348 | switch (mario.motionState) { 1349 | case STANDING: motionState = "STANDING"; 1350 | break; 1351 | case RUNNING: motionState = "RUNNING"; 1352 | break; 1353 | case JUMPING: motionState = "JUMPING"; 1354 | break; 1355 | case FALLING: motionState = "FALLING"; 1356 | break; 1357 | default: ; 1358 | break; 1359 | } 1360 | packet.motionState = motionState; 1361 | String faceState = ""; 1362 | switch (mario.faceState) { 1363 | case FACE_FRONT: faceState = "FACE_FRONT"; 1364 | break; 1365 | case FACE_LEFT: faceState = "FACE_LEFT"; 1366 | break; 1367 | case FACE_RIGHT: faceState = "FACE_RIGHT"; 1368 | break; 1369 | default: ; 1370 | break; 1371 | } 1372 | packet.faceState = faceState; 1373 | packet.boostJumping = mario.boostJumping; 1374 | packet.jumpPressed = mario.jumpPressed; 1375 | switch (mario.arrowX) { 1376 | case LEFT: packet.arrowX = "LEFT"; 1377 | break; 1378 | case RIGHT: packet.arrowX = "RIGHT"; 1379 | break; 1380 | default: packet.arrowX = "NONE"; 1381 | break; 1382 | } 1383 | switch (mario.arrowY) { 1384 | case UP: packet.arrowY = "UP"; 1385 | break; 1386 | case DOWN: packet.arrowY = "DOWN"; 1387 | break; 1388 | default: packet.arrowY = "NONE"; 1389 | break; 1390 | } 1391 | packet.frictionalForce = mario.frictionalForce; 1392 | 1393 | return packet; 1394 | } 1395 | 1396 | public Packet makeGameOverPacket(int whoWon) { 1397 | Packet packet = new Packet(); 1398 | packet.type = "GAME_OVER"; 1399 | packet.whoWon = whoWon; 1400 | return packet; 1401 | } 1402 | 1403 | public String packetToString(Packet packet) { 1404 | return gson.toJson(packet); 1405 | } 1406 | 1407 | public Packet stringToPacket(String packetStr) { 1408 | return gson.fromJson(packetStr, Packet.class); 1409 | } 1410 | } 1411 | 1412 | 1413 | // ============================= NEW FILE ================================== 1414 | 1415 | 1416 | class UIManger { 1417 | public LinkedList inPackets; 1418 | public LinkedList outPackets; 1419 | 1420 | public UIManger() { 1421 | inPackets = new LinkedList(); 1422 | outPackets = new LinkedList(); 1423 | } 1424 | 1425 | public void update() { 1426 | // inPackets 1427 | for (int i = 0; i < inPackets.size(); ++i) { 1428 | inPackets.get(i).update(); 1429 | } 1430 | // GC 1431 | while (true) { 1432 | if (inPackets.isEmpty()) break; 1433 | 1434 | TextSprite textSprite = inPackets.getFirst(); 1435 | if (textSprite.y > HEIGHT) { 1436 | inPackets.removeFirst(); 1437 | } else break; 1438 | } 1439 | 1440 | // outPackets 1441 | for (int i = 0; i < outPackets.size(); ++i) { 1442 | outPackets.get(i).update(); 1443 | } 1444 | // GC 1445 | while (true) { 1446 | if (outPackets.isEmpty()) break; 1447 | 1448 | TextSprite textSprite = outPackets.getFirst(); 1449 | if (textSprite.y < 0) { 1450 | outPackets.removeFirst(); 1451 | } else break; 1452 | } 1453 | } 1454 | 1455 | public void display() { 1456 | if (! SHOW_PACKET_INDICATOR) return; 1457 | 1458 | // inPackets 1459 | for (int i = 0; i < inPackets.size(); ++i) { 1460 | inPackets.get(i).display(); 1461 | } 1462 | 1463 | // outPackets 1464 | for (int i = 0; i < outPackets.size(); ++i) { 1465 | outPackets.get(i).display(); 1466 | } 1467 | } 1468 | 1469 | public void addInPacket() { 1470 | inPackets.addLast(new TextSprite("@", 30, WIDTH, 0, 20));; 1471 | } 1472 | 1473 | public void addOutPacket() { 1474 | outPackets.addLast(new TextSprite("@", 30, 0, HEIGHT, -20));; 1475 | } 1476 | } 1477 | } --------------------------------------------------------------------------------