//////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: Pong.cs // // Note: // using System; using System.Text; using System.GC; using System.Runtime.Remoting; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Threading; using System.Collections; using System.Diagnostics; using Microsoft.Singularity; using Microsoft.Singularity.Directory; using Microsoft.Singularity.V1.Services; using Microsoft.Singularity.Io; using Microsoft.Singularity.Channels; using Keyboard = Microsoft.Singularity.Io.Keyboard; namespace Microsoft.Singularity.Applications { public class Pong { const uint Black = 0x000000; const uint Blue = 0x0000ff; const uint Green = 0x00ff00; const uint Red = 0xff0000; const uint White = 0xffffff; const int screenWidth = 1024; const int screenHeight = 768; const int paddleWidth = 16; const int paddleHeight = 128; const int ballSize = 8; const double paddleSpeed = 0.20; const double ballSpeed = 0.35; const double ballStartAngle = Math.PI / 8; const double ballMaxAngle = Math.PI / 3; const int screenMinX = 0; const int screenMinY = 0; const int screenMaxX = screenMinX + screenWidth - 1; const int screenMaxY = screenMinY + screenHeight - 1; const int paddleMin = screenMinY; const int paddleMax = screenMaxY - paddleHeight; const int ballMinX = screenMinX + paddleWidth; const int ballMaxX = screenMaxX - paddleWidth - ballSize; const int ballMinY = screenMinY; const int ballMaxY = screenMaxY - ballSize; const int paddleInit = screenMinY + ((screenHeight - paddleHeight) / 2); static double ballX = 0.0; static double ballY = 0.0; static double ballAngle = 0.0; static int ballDrawX = 0; static int ballDrawY = 0; static double[] paddlesY = new double [2] { paddleInit, paddleInit }; static int[] paddlesDrawX = new int [2] { screenMinX, screenMaxX - paddleWidth + 1 }; static int[] paddlesDrawY = new int [2] { paddleInit, paddleInit }; static bool[] inputUp = new bool [2] { false, false }; static bool[] inputDown = new bool [2] { false, false }; static int[] scores = new int [2] { 0, 0 }; static int lastWinner = 0; static bool done = false; static bool playing = false; static Random random = new Random(); public static KeyboardDeviceContract.Imp:Ready OpenKeyboard(string! devName) { KeyboardDeviceContract.Exp! exp; KeyboardDeviceContract.Imp! imp; KeyboardDeviceContract.NewChannel(out imp, out exp); // get NS endpoint DirectoryServiceContract.Imp ns = DirectoryService.NewClientEndpoint(); bool success = false; ErrorCode errorOut; bool ok = SdsUtils.Bind(devName, ns, exp, out errorOut); if (!ok) { if (errorOut == ErrorCode.ChannelClosed) { throw new Exception("Encountered a ChannelClosed while opening Keyboard"); } } else { success = true; } /* // ns.SendBind(Bitter.FromString2(devName),exp); switch receive { case ns.AckBind(): success = true; break; case ns.NakBind(rejected,error): delete rejected; break; case ns.ChannelClosed(): delete ns; throw new Exception("ns unexpectedly closed"); } */ if (!success) { DebugStub.WriteLine("OpenKeyboard lookup of {0} failed\n", __arglist(devName)); delete imp; delete ns; return null; } switch receive { case imp.Success(): break; case imp.ContractNotSupported(): throw new Exception("Contract not supported"); case imp.ChannelClosed(): throw new Exception("Didn't imp.RecvActConnect"); } delete ns; return imp; } public static VideoDeviceContract.Imp:Ready OpenVideo(string! devName) { VideoDeviceContract.Exp! exp; VideoDeviceContract.Imp! imp; VideoDeviceContract.NewChannel(out imp, out exp); // get NS endpoint DirectoryServiceContract.Imp ns = DirectoryService.NewClientEndpoint(); bool success = false; ns.SendBind(Bitter.FromString2(devName),exp); switch receive { case ns.AckBind(): success = true; break; case ns.NakBind(rejected, error): delete rejected; break; case ns.ChannelClosed(): delete ns; throw new Exception("ns unexpectedly closed"); } if (!success) { DebugStub.WriteLine("OpenVideo lookup of {0} failed\n", __arglist(devName)); delete imp; delete ns; return null; } switch receive { case imp.Success(): break; case imp.ContractNotSupported(): throw new Exception("Contract not supported"); case imp.ChannelClosed(): throw new Exception("Didn't imp.RecvActConnect"); } delete ns; return imp; } private static void DrawPaddle(VideoDeviceContract.Imp:Ready! video, int paddle, bool erase) { video.SendFill(paddlesDrawX[paddle], paddlesDrawY[paddle], paddlesDrawX[paddle] + paddleWidth - 1, paddlesDrawY[paddle] + paddleHeight - 1, erase ? Black : Blue); video.RecvAckFill(); } private static void RedrawPaddle(VideoDeviceContract.Imp:Ready! video, int paddle) { if (paddlesDrawY[paddle] != (int) paddlesY[paddle]) { DrawPaddle(video, paddle, true); paddlesDrawY[paddle] = (int) paddlesY[paddle]; DrawPaddle(video, paddle, false); } } private static void MovePaddle(VideoDeviceContract.Imp:Ready! video, int paddle) { paddlesY[paddle] += GetPaddleState(paddle) * paddleSpeed; if (paddlesY[paddle] > paddleMax) { paddlesY[paddle] = paddleMax; } else if (paddlesY[paddle] < paddleMin) { paddlesY[paddle] = paddleMin; } RedrawPaddle(video, paddle); } private static bool PaddleHit(int paddle) { return ((ballY > paddlesY[paddle] - ballSize) && (ballY < paddlesY[paddle] + paddleHeight)); } private static void ResetBall() { ballY = screenMinY + ((screenHeight - ballSize) / 2); ballAngle = (random.NextDouble() * ballStartAngle) - (ballStartAngle / 2); if (lastWinner == 0) { ballX = ballMinX; if (ballAngle < 0) { ballAngle += 2 * Math.PI; } } else { ballX = ballMaxX; ballAngle += Math.PI; } ballDrawX = (int) ballX; ballDrawY = (int) ballY; } private static void DrawBall(VideoDeviceContract.Imp:Ready! video, bool erase) { video.SendFill(ballDrawX, ballDrawY, ballDrawX + ballSize - 1, ballDrawY + ballSize - 1, erase ? Black : Red); video.RecvAckFill(); } private static void RedrawBall(VideoDeviceContract.Imp:Ready! video) { if (ballDrawX != (int) ballX || ballDrawY != (int) ballY) { DrawBall(video, true); ballDrawX = (int) ballX; ballDrawY = (int) ballY; DrawBall(video, false); } } private static void ReflectBallY(int line) { ballY = (2 * line) - ballY; ballAngle = (2 * Math.PI) - ballAngle; } private static void SetAngle(int paddle) { // Find out where the ball hit the paddle, from -1 to 1. double ratio = ((ballY + (ballSize / 2)) - (paddlesY[paddle] + (paddleHeight / 2))) / ((paddleHeight + ballSize) / 2); // Set the new angle accordingly. ballAngle = ratio * ballMaxAngle; if (paddle == 0) { if (ballAngle < 0) { ballAngle += 2 * Math.PI; } } else { ballAngle = Math.PI - ballAngle; } } private static void MoveBall(VideoDeviceContract.Imp:Ready! video) { ballY += Math.Sin(ballAngle) * ballSpeed; if (ballY < ballMinY) { ReflectBallY(ballMinY); } else if (ballY > ballMaxY) { ReflectBallY(ballMaxY); } ballX += Math.Cos(ballAngle) * ballSpeed; if (ballX < ballMinX) { if (PaddleHit(0)) { SetAngle(0); } else { lastWinner = 1; playing = false; } } else if (ballX > ballMaxX) { if (PaddleHit(1)) { SetAngle(1); } else { lastWinner = 0; playing = false; } } RedrawBall(video); } private static int GetPaddleState(int paddle) { if (inputUp[paddle]) { return -1; } else if (inputDown[paddle]) { return 1; } else { return 0; } } private static void SetStateFromChar(char c, bool @state) { switch (c) { case (char)Keyboard.Keys.UP_ARROW: inputUp[1] = @state; break; case (char)Keyboard.Keys.DOWN_ARROW: inputDown[1] = @state; break; case 'a': inputUp[0] = @state; break; case 'z': inputDown[0] = @state; break; } } private static void FillScreen(VideoDeviceContract.Imp:Ready! video, uint color) { video.SendFill(screenMinX, screenMinY, screenMaxX, screenMaxY, color); video.RecvAckFill(); } private static void ClearScreen(VideoDeviceContract.Imp:Ready! video) { FillScreen(video, Black); } private static void FlashScreen(VideoDeviceContract.Imp:Ready! video) { FillScreen(video, Red); Sleep(1000); FillScreen(video, Green); Sleep(1000); FillScreen(video, Blue); Sleep(1000); } private static void ShowMessage(VideoDeviceContract.Imp:Ready! video) { ClearScreen(video); for (int i = 0; i < 100; i++) { Console.WriteLine(); } Console.Write(" "); Console.Write("Left: {0,-10}", scores[0]); Console.Write(" "); Console.Write("Right: {0,-10}", scores[1]); Console.WriteLine(); Console.WriteLine(); Console.Write(" "); Console.Write("Press space to begin or escape to quit..."); Console.WriteLine(); DrawPaddle(video, 0, false); DrawPaddle(video, 1, false); DrawBall(video, false); } private static void Sleep(int multiplier) { for (int j = 0; j < multiplier; j++) { for (int i = 0; i < 10000; i++) { // Do nothing. } } } public static int Main(string[] args) { VideoDeviceContract.Imp video = OpenVideo("/dev/video"); Console.WriteLine("Singularity Pong (PID={0})", ProcessService.GetCurrentProcessId()); if (video == null) { Console.WriteLine("Can only play pong on a graphic display."); return 1; } KeyboardDeviceContract.Imp keyboard = OpenKeyboard("/dev/keyboard"); if (keyboard == null) { // TODO: show keyboardless slides with timer? delete video; return 1; } ShowMessage(video); while (!done) { uint key = 0; keyboard.SendPollKey(); switch receive { case keyboard.AckKey(ikey): key = ikey; break; case keyboard.NakKey(): key = 0; break; case keyboard.ChannelClosed(): throw new Exception("Didn't get keyboard closed."); } char c = (char) (key & (uint) Keyboard.Qualifiers.KEY_BASE_CODE); if ((key & (uint) Keyboard.Qualifiers.KEY_DOWN) != 0) { switch (c) { case (char) Keyboard.Keys.ESCAPE: done = true; break; case ' ': if (!playing) { ResetBall(); ClearScreen(video); DrawPaddle(video, 0, false); DrawPaddle(video, 1, false); DrawBall(video, false); playing = true; } break; default: SetStateFromChar(c, true); break; } } else if ((key & (uint) Keyboard.Qualifiers.KEY_UP) != 0) { SetStateFromChar(c, false); } if (playing) { MovePaddle(video, 0); MovePaddle(video, 1); MoveBall(video); if (!playing) { scores[lastWinner]++; FlashScreen(video); ShowMessage(video); } } Sleep(10); } ClearScreen(video); delete keyboard; delete video; return 0; } } }